From 2e7640507a2439d323c0d64b7d18ba49fc0f11da Mon Sep 17 00:00:00 2001 From: kcmvp Date: Thu, 16 May 2024 14:09:55 +0800 Subject: [PATCH] `#23: implement type rules --- function.go | 47 +++- go.mod | 26 ++- go.sum | 76 ++++++- internal/artifact.go | 287 ++++++++++++++---------- internal/artifact_test.go | 178 +++++++++------ internal/sample/controller/handlers.go | 98 ++++++++ internal/sample/main.go | 12 + internal/sample/service/user_service.go | 11 + layer.go | 171 ++++++-------- layer_test.go | 75 ++----- package.go | 49 +++- package_test.go | 28 +++ type.go | 82 +++++-- type_test.go | 79 +++++++ 14 files changed, 848 insertions(+), 371 deletions(-) create mode 100644 internal/sample/controller/handlers.go create mode 100644 internal/sample/main.go create mode 100644 package_test.go create mode 100644 type_test.go diff --git a/function.go b/function.go index 6aed3b8..e893461 100644 --- a/function.go +++ b/function.go @@ -2,39 +2,68 @@ package archunit import ( + "github.com/kcmvp/archunit/internal" "github.com/samber/lo" + "log" ) -type Function lo.Tuple2[string, []string] +type Functions []lo.Tuple2[string, []string] -func (functions Function) Exclude(names ...string) Function { +func FunctionsOfType(fTypName string) Functions { + typ, ok := internal.Arch().Type(fTypName) + if !ok || !typ.Func() { + log.Fatalf("can not find function type %s", fTypName) + } + lo.ForEach(lo.Filter(internal.Arch().Packages(), func(pkg *internal.Package, _ int) bool { + return lo.Contains(pkg.Imports(), typ.Package()) + }), func(pkg *internal.Package, _ int) { + panic("should not reach here") + }) + //for _, pkg := range internal.Arch().packages() { + // if strings.HasSuffix(pkg.ID(), "github.com/kcmvp/archunit/internal/sample/service") { + // for _, f := range pkg.RawFunctions() { + // if types.Identical(typ.TypeValue().Underlying(), f.Type()) { + // println(f.FullName()) + // } + // } + // } + //} + + return Functions{} +} + +func (functions Functions) Exclude(names ...string) Functions { + panic("to be implemented") +} + +func (functions Functions) InPackage(paths ...string) Functions { panic("to be implemented") } -func (functions Function) InPackage(paths ...string) Function { +func (functions Functions) OfType(types ...string) Functions { panic("to be implemented") } -func (functions Function) OfType(types ...string) Function { +func (functions Functions) WithReturn() Functions { panic("to be implemented") } -func (functions Function) WithReturn() Function { +func (functions Functions) WithParameter() Functions { panic("to be implemented") } -func (functions Function) WithParameter() Function { +func (functions Functions) ShouldBeInPackage(pkgPath ...string) error { panic("to be implemented") } -func (functions Function) ShouldBePrivate() error { +func (functions Functions) ShouldBe(visibility Visibility) error { panic("to be implemented") } -func (functions Function) ShouldBePublic() error { +func (functions Functions) NameShould(pattern NamePattern) error { panic("to be implemented") } -func (functions Function) NameShould(pattern NamePattern) error { +func (functions Functions) NoAnonymous() error { panic("to be implemented") } diff --git a/go.mod b/go.mod index 9411dd1..f5da9a8 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,43 @@ go 1.21.4 require ( github.com/fatih/color v1.16.0 + github.com/gin-gonic/gin v1.10.0 github.com/samber/lo v1.39.0 github.com/stretchr/testify v1.9.0 golang.org/x/tools v0.20.0 ) require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5fe3d53..3444f74 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,103 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/artifact.go b/internal/artifact.go index 6dcd9c1..07c786d 100644 --- a/internal/artifact.go +++ b/internal/artifact.go @@ -2,6 +2,9 @@ package internal import ( "fmt" + "github.com/fatih/color" + "github.com/samber/lo" + lop "github.com/samber/lo/parallel" "go/types" "log" "os/exec" @@ -9,39 +12,48 @@ import ( "strings" "sync" - "github.com/fatih/color" - "github.com/samber/lo" - "golang.org/x/tools/go/packages" ) +type ParseMode int + +const ( + ParseCon ParseMode = 1 << iota + ParseFun + ParseTyp + ParseVar +) + var ( once sync.Once arch *Artifact ) -type ( - Param lo.Tuple2[string, string] - Function lo.Tuple4[string, []Param, []string, string] -) - type Package struct { raw *packages.Package constantsDef []string - functions []*types.Func + functions []Function types []Type } +type Param lo.Tuple2[string, string] + +type Function struct { + raw *types.Func +} + type Type struct { - named *types.Named - pkg *packages.Package - interfaces bool + raw *types.Named } +type Variable struct { + pkg string + raw *types.Named +} type Artifact struct { rootDir string module string - pkgs []*Package + pkgs sync.Map } func (artifact *Artifact) RootDir() string { @@ -70,10 +82,9 @@ func Arch() *Artifact { color.Red("Error loading project: %w", err) return } - for _, pkg := range pkgs { - arch.pkgs = append(arch.pkgs, &Package{raw: pkg}) - } - arch.parse() + lop.ForEach(pkgs, func(pkg *packages.Package, _ int) { + arch.pkgs.Store(pkg.ID, parse(pkg, ParseCon|ParseFun|ParseTyp)) + }) }) return arch } @@ -91,133 +102,99 @@ func PkgPattern(path string) (*regexp.Regexp, error) { path = strings.ReplaceAll(path, "...", ".*") return regexp.MustCompile(fmt.Sprintf("%s$", path)), nil } -func (artifact *Artifact) parse() { - for _, pkg := range artifact.pkgs { - typPkg := pkg.raw.Types - if typPkg == nil { - continue - } - scope := typPkg.Scope() - for _, name := range scope.Names() { - obj := scope.Lookup(name) - file := pkg.raw.Fset.Position(obj.Pos()).Filename - if _, ok := obj.(*types.Const); ok { - if !lo.Contains(pkg.constantsDef, file) { - pkg.constantsDef = append(pkg.constantsDef, file) - } - } else if fObj, ok := obj.(*types.Func); ok { - pkg.functions = append(pkg.functions, fObj) - } else if _, ok = obj.(*types.TypeName); ok { - if namedType, ok := obj.Type().(*types.Named); ok { - typ := Type{named: namedType, pkg: pkg.raw} - if _, ok := namedType.Underlying().(*types.Interface); ok { - typ.interfaces = true - } - pkg.types = append(pkg.types, typ) + +func parse(pkg *packages.Package, mode ParseMode) *Package { + archPkg := &Package{raw: pkg} + typPkg := pkg.Types + scope := typPkg.Scope() + lo.ForEach(scope.Names(), func(name string, _ int) { + obj := scope.Lookup(name) + file := pkg.Fset.Position(obj.Pos()).Filename + switch vType := obj.(type) { + case *types.Const: + if ParseCon&mode == ParseCon && !lo.Contains(archPkg.constantsDef, file) { + archPkg.constantsDef = append(archPkg.constantsDef, file) + } + case *types.Func: + if ParseFun&mode == ParseFun { + archPkg.functions = append(archPkg.functions, Function{raw: vType}) + } + case *types.TypeName: + if ParseTyp&mode == ParseTyp { + if namedType, ok := vType.Type().(*types.Named); ok { + archPkg.types = append(archPkg.types, Type{raw: namedType}) } } + case *types.Var: + if ParseVar&mode == ParseVar { + panic("unreachable") + } } - } + }) + return archPkg } func (artifact *Artifact) Packages() []*Package { - return artifact.pkgs -} - -func (artifact *Artifact) Package(path string) (*Package, bool) { - return lo.Find(artifact.pkgs, func(pkg *Package) bool { - return pkg.raw.ID == path + var pkgs []*Package + artifact.pkgs.Range(func(_, value any) bool { + pkgs = append(pkgs, value.(*Package)) + return true }) + return pkgs } -func (artifact *Artifact) AllPackages() []lo.Tuple2[string, string] { - return lo.Map(artifact.pkgs, func(item *Package, _ int) lo.Tuple2[string, string] { - return lo.Tuple2[string, string]{A: item.raw.ID, B: item.raw.Name} - }) +func (artifact *Artifact) Package(id string) *Package { + if pkg, ok := artifact.pkgs.Load(id); ok { + return pkg.(*Package) + } + return nil } -func (artifact *Artifact) AllSources() []string { +func (artifact *Artifact) GoFiles() []string { var files []string - for _, pkg := range artifact.pkgs { + for _, pkg := range artifact.Packages() { files = append(files, pkg.raw.GoFiles...) } return files } -func (artifact *Artifact) Types() []Type { - var types []Type - for _, pkg := range artifact.pkgs { - types = append(types, pkg.types...) - } - return types -} - -func (typ Type) Name() string { - return typ.named.String() -} - -func (typ Type) Interface() bool { - return typ.interfaces -} - -func (typ Type) TypeValue() *types.Named { - return typ.named -} - -func (typ Type) Functions() []Function { - var functions []Function - if typ.interfaces { - iTyp := typ.named.Underlying().(*types.Interface) - n := iTyp.NumMethods() - for i := 0; i < n; i++ { - method := iTyp.Method(i) - functions = append(functions, function(typ.pkg, method)) +// Type returns the type of specified type name, return false when can not find the type +// typName type name. You can just use short name of types in current module eg: internal/sample/service.UserService +// for the types from dependency a full qualified type name must be supplied eg: github.com/gin-gonic/gin.Context +func (artifact *Artifact) Type(typName string) (Type, bool) { + prefix := strings.Split(typName, "/")[0] + typName = lo.If(strings.Contains(prefix, "."), typName).Else(fmt.Sprintf("%s/%s", artifact.Module(), typName)) + pkgName := strings.Join(lo.DropRight(strings.Split(typName, "."), 1), ".") + pkg, ok := artifact.pkgs.Load(pkgName) + if !ok { + for _, e := range artifact.Packages() { + if strings.HasPrefix(e.ID(), artifact.Module()) { + if raw, ok := e.raw.Imports[pkgName]; ok { + pkg = parse(raw, ParseTyp|ParseFun) + artifact.pkgs.Store(pkgName, pkg) + break + } + } } - } else { - n := typ.named.NumMethods() - for i := 0; i < n; i++ { - fObj := typ.named.Method(i) - functions = append(functions, function(typ.pkg, fObj)) + if pkg == nil { + return Type{}, false } } - return functions + return lo.Find(pkg.(*Package).types, func(typ Type) bool { + return typ.raw.String() == typName + }) } -func (artifact *Artifact) FunctionsOfType(typeName string) []Function { - if typ, ok := lo.Find(artifact.Types(), func(typ Type) bool { - return strings.HasSuffix(typ.Name(), typeName) - }); ok { - return typ.Functions() - } - return []Function{} +func (pkg *Package) Raw() *packages.Package { + return pkg.raw } func (pkg *Package) ConstantFiles() []string { return pkg.constantsDef } -func function(pkg *packages.Package, fObj *types.Func) Function { - wf := Function{A: fObj.FullName(), D: pkg.Fset.Position(fObj.Pos()).Filename} - signature := fObj.Type().(*types.Signature) - if params := signature.Params(); params != nil { - for i := params.Len() - 1; i >= 0; i-- { - param := params.At(i) - wf.B = append(wf.B, Param{A: param.Name(), B: param.Type().String()}) - } - } - if rts := signature.Results(); rts != nil { - for i := rts.Len() - 1; i >= 0; i-- { - rt := rts.At(i) - wf.C = append(wf.C, rt.Type().String()) - } - } - return wf -} - func (pkg *Package) Functions() []Function { - return lo.Map(pkg.functions, func(f *types.Func, index int) Function { - return function(pkg.raw, f) - }) + return pkg.functions } func (pkg *Package) Types() []Type { @@ -236,14 +213,86 @@ func (pkg *Package) Imports() []string { return lo.Keys(pkg.raw.Imports) } +func (pkg *Package) Name() string { + return pkg.raw.Name +} + +func (pkg *Package) Path() string { + return pkg.raw.PkgPath +} + +func (typ Type) Interface() bool { + _, ok := typ.raw.Underlying().(*types.Interface) + return ok +} + +func (typ Type) Package() string { + return typ.raw.Obj().Pkg().Path() +} + +func (typ Type) Func() bool { + panic("not implemented") +} + +func (typ Type) Raw() *types.Named { + return typ.raw +} + +func (typ Type) Name() string { + return typ.raw.String() +} + +func (typ Type) GoFile() string { + return Arch().Package(typ.Package()).raw.Fset.Position(typ.raw.Obj().Pos()).Filename +} + +func (typ Type) Methods() []Function { + var functions []Function + if typ.Interface() { + iTyp := typ.raw.Underlying().(*types.Interface) + n := iTyp.NumMethods() + for i := 0; i < n; i++ { + functions = append(functions, Function{raw: iTyp.Method(i)}) + } + } else { + n := typ.raw.NumMethods() + for i := 0; i < n; i++ { + functions = append(functions, Function{raw: typ.raw.Method(i)}) + } + } + return functions +} + func (f Function) Name() string { - return f.A + return f.raw.Name() +} + +func (f Function) Package() string { + return f.raw.Pkg().Path() +} + +func (f Function) GoFile() string { + return Arch().Package(f.Package()).raw.Fset.Position(f.raw.Pos()).Filename } func (f Function) Params() []Param { - return f.B + var params []Param + if tuple := f.raw.Type().(*types.Signature).Params(); tuple != nil { + for i := tuple.Len() - 1; i >= 0; i-- { + param := tuple.At(i) + params = append(params, Param{A: param.Name(), B: param.Type().String()}) + } + } + return params } -func (f Function) Returns() []string { - return f.C +func (f Function) Returns() []Param { + var rt []Param + if rs := f.raw.Type().(*types.Signature).Results(); rs != nil { + for i := rs.Len() - 1; i >= 0; i-- { + param := rs.At(i) + rt = append(rt, Param{A: param.Name(), B: param.Type().String()}) + } + } + return rt } diff --git a/internal/artifact_test.go b/internal/artifact_test.go index 795cb7c..8a383f5 100644 --- a/internal/artifact_test.go +++ b/internal/artifact_test.go @@ -63,19 +63,33 @@ func Test_pattern(t *testing.T) { } func TestAllConstants(t *testing.T) { - expected := []string{"archunit/internal/sample/repository/constants.go", - "archunit/internal/sample/repository/user_repository.go"} - for _, pkg := range Arch().Packages() { - if pkg.ID() == "github.com/kcmvp/archunit/internal/sample/repository" { - assert.True(t, lo.EveryBy(pkg.ConstantFiles(), func(item string) bool { - return lo.SomeBy(expected, func(exp string) bool { - return strings.HasSuffix(item, exp) + tests := []struct { + pkg string + files []string + }{ + { + pkg: "github.com/kcmvp/archunit/internal/sample/repository", + files: []string{"archunit/internal/sample/repository/constants.go", + "archunit/internal/sample/repository/user_repository.go"}, + }, + { + pkg: "github.com/kcmvp/archunit", + files: []string{"archunit/layer.go"}, + }, + } + for _, test := range tests { + t.Run(test.pkg, func(t *testing.T) { + pkg := Arch().Package(test.pkg) + assert.NotNil(t, pkg) + assert.Equal(t, len(test.files), len(pkg.ConstantFiles())) + lo.EveryBy(test.files, func(f1 string) bool { + return lo.EveryBy(pkg.ConstantFiles(), func(f2 string) bool { + return strings.HasSuffix(f2, f1) }) - })) - } else { - assert.Equal(t, 0, len(pkg.ConstantFiles())) - } + }) + }) } + } func TestPackage_Functions(t *testing.T) { @@ -87,9 +101,11 @@ func TestPackage_Functions(t *testing.T) { }{ { pkg: "github.com/kcmvp/archunit/internal", - funcs: []string{"github.com/kcmvp/archunit/internal.Arch", - "github.com/kcmvp/archunit/internal.PkgPattern", - "github.com/kcmvp/archunit/internal.function"}, + funcs: []string{ + "Arch", + "PkgPattern", + "parse", + }, imports: []string{ "fmt", "os/exec", @@ -101,23 +117,25 @@ func TestPackage_Functions(t *testing.T) { "strings", "sync", "github.com/fatih/color", + "github.com/samber/lo/parallel", }, exists: true, }, { pkg: "github.com/kcmvp/archunit", - funcs: []string{"github.com/kcmvp/archunit.LowerCase", - "github.com/kcmvp/archunit.UpperCase", - "github.com/kcmvp/archunit.ConstantsShouldBeDefinedInOneFileByPackage", - "github.com/kcmvp/archunit.HavePrefix", - "github.com/kcmvp/archunit.HaveSuffix", - "github.com/kcmvp/archunit.MethodsOfTypeShouldBeDefinedInSameFile", - "github.com/kcmvp/archunit.PackageNameShouldBe", - "github.com/kcmvp/archunit.PackageNameShouldBeSameAsFolderName", - "github.com/kcmvp/archunit.Packages", - "github.com/kcmvp/archunit.SourceNameShouldBe", - "github.com/kcmvp/archunit.TypeEmbeddedWith", - "github.com/kcmvp/archunit.TypeImplement", + funcs: []string{ + "BeLowerCase", + "BeUpperCase", + "ConstantsShouldBeDefinedInOneFileByPackage", + "FunctionsOfType", + "HavePrefix", + "HaveSuffix", + "Lay", + "AllTypes", + "AllPackages", + "SourceNameShould", + "TypesEmbeddedWith", + "TypesImplement", }, imports: []string{ "fmt", @@ -128,23 +146,28 @@ func TestPackage_Functions(t *testing.T) { "path/filepath", "regexp", "strings", + "github.com/samber/lo/parallel", + "sync", }, exists: true, }, { pkg: "github.com/kcmvp/archunit/internal/sample", - funcs: []string{"github.com/kcmvp/archunit/internal/sample.PrintImplementations", - "github.com/kcmvp/archunit/internal/sample.findInterface", - "github.com/kcmvp/archunit/internal/sample.loadPkgs", - "github.com/kcmvp/archunit/internal/sample.main", - "github.com/kcmvp/archunit/internal/sample.printImplementation"}, - imports: []string{}, - exists: false, + funcs: []string{ + "start", + }, + imports: []string{ + "github.com/gin-gonic/gin", + }, + exists: true, }, { - pkg: "github.com/kcmvp/archunit/internal/sample/service", - funcs: []string{}, + pkg: "github.com/kcmvp/archunit/internal/sample/service", + funcs: []string{ + "AuditCall", + }, imports: []string{ + "context", "github.com/kcmvp/archunit/internal/sample/repository", "github.com/kcmvp/archunit/internal/sample/model", }, @@ -162,11 +185,11 @@ func TestPackage_Functions(t *testing.T) { } for _, test := range tests { t.Run(test.pkg, func(t *testing.T) { - pkg, ok := Arch().Package(test.pkg) - assert.Equal(t, ok, test.exists) - if ok { + pkg := Arch().Package(test.pkg) + assert.Equal(t, lo.If(pkg == nil, false).Else(true), test.exists) + if pkg != nil { funcs := lo.Map(pkg.Functions(), func(item Function, _ int) string { - return item.A + return item.Name() }) assert.ElementsMatch(t, test.funcs, funcs) assert.ElementsMatch(t, test.imports, pkg.Imports()) @@ -177,60 +200,69 @@ func TestPackage_Functions(t *testing.T) { } func TestAllSource(t *testing.T) { - assert.Equal(t, 20, len(Arch().AllSources())) + assert.Equal(t, 22, len(Arch().GoFiles())) } -func TestFunctionsOfType(t *testing.T) { +func TestMethodsOfType(t *testing.T) { tests := []struct { typName string + exists bool functions []string }{ { typName: "internal/sample/service.UserService", + exists: true, functions: []string{ - "(github.com/kcmvp/archunit/internal/sample/service.UserService).GetUserById", - "(github.com/kcmvp/archunit/internal/sample/service.UserService).GetUserByNameAndAddress", - "(github.com/kcmvp/archunit/internal/sample/service.UserService).SearchUsersByFirstName", - "(*github.com/kcmvp/archunit/internal/sample/service.UserService).SearchUsersByLastName"}, + "GetUserById", + "GetUserByNameAndAddress", + "SearchUsersByFirstName", + "SearchUsersByLastName", + }, }, { typName: "internal/sample/service.NameService", + exists: true, functions: []string{ - "(github.com/kcmvp/archunit/internal/sample/service.NameService).FirstNameI", - "(github.com/kcmvp/archunit/internal/sample/service.NameService).LastNameI", + "FirstNameI", + "LastNameI", }, }, { typName: "internal/sample/service.NameService1", + exists: false, + functions: []string{}, + }, + { + typName: "internal/sample/service.Audit", + exists: true, functions: []string{}, }, } for _, test := range tests { t.Run(test.typName, func(t *testing.T) { - functions := Arch().FunctionsOfType(test.typName) - fs := lo.Map(functions, func(item Function, _ int) string { - return item.Name() - }) - assert.ElementsMatch(t, test.functions, fs) - if f, ok := lo.Find(functions, func(item Function) bool { - return strings.HasSuffix(item.Name(), "service.UserService).SearchUsersByFirstName") - }); ok { - assert.Equal(t, f.Params(), []Param{ - {"firstName", "string"}, - }) - assert.Equal(t, f.Returns(), []string{ - "error", "[]github.com/kcmvp/archunit/internal/sample/model.User", + typ, ok := Arch().Type(test.typName) + assert.Equal(t, ok, test.exists) + if ok { + funcs := lo.Map(typ.Methods(), func(item Function, _ int) string { + return item.Name() }) + assert.ElementsMatch(t, funcs, test.functions) + if f, ok := lo.Find(typ.Methods(), func(item Function) bool { + return strings.HasSuffix(item.Name(), "service.UserService).SearchUsersByFirstName") + }); ok { + assert.Equal(t, f.Params(), []Param{ + {"firstName", "string"}, + }) + assert.Equal(t, f.Returns(), []string{ + "error", "[]github.com/kcmvp/archunit/internal/sample/model.User", + }) + } } - }) } } func TestArtifact_AllPackages(t *testing.T) { - allPkgs := lo.Map(Arch().AllPackages(), func(item lo.Tuple2[string, string], _ int) string { - return item.A - }) expPkgs := []string{"github.com/kcmvp/archunit/internal", "github.com/kcmvp/archunit", "github.com/kcmvp/archunit/internal/sample/model", @@ -244,9 +276,13 @@ func TestArtifact_AllPackages(t *testing.T) { "github.com/kcmvp/archunit/internal/sample/repository/ext", "github.com/kcmvp/archunit/internal/sample/service/ext", "github.com/kcmvp/archunit/internal/sample/service/ext/v2", - "github.com/kcmvp/archunit/internal/sample/service/thirdparty"} - assert.Equal(t, 14, len(allPkgs)) - assert.ElementsMatch(t, expPkgs, allPkgs) + "github.com/kcmvp/archunit/internal/sample/service/thirdparty", + "github.com/kcmvp/archunit/internal/sample", + } + keys := lo.Map(Arch().Packages(), func(item *Package, _ int) string { + return item.ID() + }) + assert.ElementsMatch(t, expPkgs, keys) } func TestPkgTypes(t *testing.T) { @@ -263,7 +299,9 @@ func TestPkgTypes(t *testing.T) { "github.com/kcmvp/archunit/internal.Function", "github.com/kcmvp/archunit/internal.Package", "github.com/kcmvp/archunit/internal.Param", + "github.com/kcmvp/archunit/internal.ParseMode", "github.com/kcmvp/archunit/internal.Type", + "github.com/kcmvp/archunit/internal.Variable", }, valid: true, files: 1, @@ -271,6 +309,7 @@ func TestPkgTypes(t *testing.T) { { pkgName: "github.com/kcmvp/archunit/internal/sample/service", typs: []string{ + "github.com/kcmvp/archunit/internal/sample/service.Audit", "github.com/kcmvp/archunit/internal/sample/service.FullNameImpl", "github.com/kcmvp/archunit/internal/sample/service.NameService", "github.com/kcmvp/archunit/internal/sample/service.NameServiceImpl", @@ -282,8 +321,8 @@ func TestPkgTypes(t *testing.T) { } for _, test := range tests { t.Run(test.pkgName, func(t *testing.T) { - pkg, ok := Arch().Package(test.pkgName) - assert.Equal(t, ok, test.valid) + pkg := Arch().Package(test.pkgName) + assert.Equal(t, lo.If(pkg == nil, false).Else(true), test.valid) assert.Equal(t, len(test.typs), len(pkg.Types())) typs := lo.Map(pkg.Types(), func(item Type, _ int) string { return item.Name() @@ -294,7 +333,6 @@ func TestPkgTypes(t *testing.T) { return strings.HasSuffix(typ.Name(), "sample/service.NameService") }); ok { assert.True(t, typ.Interface()) - assert.NotNil(t, typ.TypeValue()) } }) } diff --git a/internal/sample/controller/handlers.go b/internal/sample/controller/handlers.go new file mode 100644 index 0000000..f6aac35 --- /dev/null +++ b/internal/sample/controller/handlers.go @@ -0,0 +1,98 @@ +// nolint +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +var SayHello gin.HandlerFunc = func(c *gin.Context) {} + +func LoginHandler(ctx gin.Context) { + +} + +type MyRouterGroup struct{} + +func (m MyRouterGroup) Use(handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) Handle(s string, s2 string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) Any(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) GET(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) POST(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) DELETE(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) PATCH(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) PUT(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) OPTIONS(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) HEAD(s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) Match(strings []string, s string, handlerFunc ...gin.HandlerFunc) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) StaticFile(s string, s2 string) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) StaticFileFS(s string, s2 string, system http.FileSystem) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) Static(s string, s2 string) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) StaticFS(s string, system http.FileSystem) gin.IRoutes { + // TODO implement me + panic("implement me") +} + +func (m MyRouterGroup) Group(s string, handlerFunc ...gin.HandlerFunc) *gin.RouterGroup { + // TODO implement me + panic("implement me") +} + +var _ gin.IRouter = (*MyRouterGroup)(nil) diff --git a/internal/sample/main.go b/internal/sample/main.go new file mode 100644 index 0000000..52c8db6 --- /dev/null +++ b/internal/sample/main.go @@ -0,0 +1,12 @@ +package main + +import "github.com/gin-gonic/gin" + +func start() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) +} diff --git a/internal/sample/service/user_service.go b/internal/sample/service/user_service.go index 12a7ed9..d42f575 100644 --- a/internal/sample/service/user_service.go +++ b/internal/sample/service/user_service.go @@ -2,10 +2,21 @@ package service import ( + "context" "github.com/kcmvp/archunit/internal/sample/model" "github.com/kcmvp/archunit/internal/sample/repository" ) +type Audit func(string, context.Context) []string + +var auditLog Audit = func(s string, ctx context.Context) []string { + return []string{} +} + +func AuditCall(id string, ctx context.Context) []string { + return []string{} +} + type NameService interface { FirstNameI() string LastNameI() string diff --git a/layer.go b/layer.go index 4dab0c9..dcea55b 100644 --- a/layer.go +++ b/layer.go @@ -5,19 +5,25 @@ import ( "fmt" "github.com/kcmvp/archunit/internal" "github.com/samber/lo" - "go/types" "log" "path/filepath" "regexp" "strings" ) +type Visibility int + +const ( + Public Visibility = iota + Private +) + type NamePattern func(name, arg string) bool -func LowerCase(name, _ string) bool { +func BeLowerCase(name, _ string) bool { return strings.ToLower(name) == name } -func UpperCase(name, _ string) bool { +func BeUpperCase(name, _ string) bool { return strings.ToUpper(name) == name } func HavePrefix(name, prefix string) bool { @@ -27,28 +33,10 @@ func HaveSuffix(name, suffix string) bool { return strings.HasSuffix(name, suffix) } -func PackageNameShouldBeSameAsFolderName() error { - if pkg, ok := lo.Find(internal.Arch().AllPackages(), func(item lo.Tuple2[string, string]) bool { - return !strings.HasSuffix(item.A, item.B) - }); ok { - return fmt.Errorf("package %s's name is %s", pkg.A, pkg.B) - } - return nil -} - -func PackageNameShouldBe(pattern NamePattern, args ...string) error { - if pkg, ok := lo.Find(internal.Arch().AllPackages(), func(item lo.Tuple2[string, string]) bool { - return !pattern(item.A, lo.If(args == nil, "").ElseF(func() string { - return args[0] - })) - }); ok { - return fmt.Errorf("package %s's name is %s", pkg.A, pkg.B) - } - return nil -} +type Layer []*internal.Package -func SourceNameShouldBe(pattern NamePattern, args ...string) error { - if file, ok := lo.Find(internal.Arch().AllSources(), func(file string) bool { +func SourceNameShould(pattern NamePattern, args ...string) error { + if file, ok := lo.Find(internal.Arch().GoFiles(), func(file string) bool { return !pattern(filepath.Base(file), lo.If(args == nil, "").ElseF(func() string { return args[0] })) @@ -58,19 +46,19 @@ func SourceNameShouldBe(pattern NamePattern, args ...string) error { return nil } -func MethodsOfTypeShouldBeDefinedInSameFile() error { - for _, pkg := range internal.Arch().Packages() { - for _, typ := range pkg.Types() { - files := lo.Uniq(lo.Map(typ.Functions(), func(item internal.Function, _ int) string { - return item.D - })) - if len(files) > 1 { - return fmt.Errorf("functions of type %s are defined in files %v", typ.Name(), files) - } - } - } - return nil -} +//func MethodsOfTypeShouldBeDefinedInSameFile() error { +// for _, pkg := range internal.Arch().Packages() { +// for _, typ := range pkg.Types() { +// files := lo.Uniq(lo.Map(typ.Methods(), func(f internal.Function, _ int) string { +// return f.GoFile() +// })) +// if len(files) > 1 { +// return fmt.Errorf("methods of type %s are defined in files %v", typ.Name(), files) +// } +// } +// } +// return nil +//} func ConstantsShouldBeDefinedInOneFileByPackage() error { for _, pkg := range internal.Arch().Packages() { @@ -82,65 +70,49 @@ func ConstantsShouldBeDefinedInOneFileByPackage() error { return nil } -func TypeEmbeddedWith(embeds ...string) Types { - panic("to be implemented") -} - -func TypeImplement(interName string) []Type { - group := lo.GroupBy(internal.Arch().Types(), func(typ internal.Type) int { - return lo.If(typ.Interface(), 0).Else(1) - }) - if inter, ok := lo.Find(group[0], func(inter internal.Type) bool { - return strings.HasSuffix(inter.Name(), interName) - }); ok { - typs := lo.Filter(group[1], func(typ internal.Type, _ int) bool { - return types.Implements(typ.TypeValue(), inter.TypeValue().Underlying().(*types.Interface)) - }) - return lo.Map(typs, func(item internal.Type, _ int) Type { - return Type{name: item.Name()} - }) - } - return []Type{} -} - -type Layer lo.Tuple2[string, []*internal.Package] - -func Packages(layerName string, paths []string) Layer { - patterns := lo.Map(paths, func(path string, _ int) *regexp.Regexp { +func Lay(pkgPaths ...string) Layer { + patterns := lo.Map(pkgPaths, func(path string, _ int) *regexp.Regexp { reg, err := internal.PkgPattern(path) if err != nil { log.Fatal(err) } return reg }) - return Layer{ - A: layerName, - B: lo.Filter(internal.Arch().Packages(), func(pkg *internal.Package, _ int) bool { - return lo.ContainsBy(patterns, func(pattern *regexp.Regexp) bool { - return pattern.MatchString(pkg.ID()) - }) - }), - } + return lo.Filter(internal.Arch().Packages(), func(pkg *internal.Package, _ int) bool { + return lo.ContainsBy(patterns, func(pattern *regexp.Regexp) bool { + return pattern.MatchString(pkg.ID()) + }) + }) } -func (layer Layer) Exclude(paths ...string) Layer { - patterns := lo.Map(paths, func(path string, _ int) *regexp.Regexp { +func (layer Layer) Name() string { + pkgs := layer.packages() + idx := 0 + left := lo.DropWhile(pkgs, func(item string) bool { + idx++ + if idx == len(pkgs) { + return false + } + return lo.ContainsBy(pkgs[idx:], func(l string) bool { + return strings.HasPrefix(l, item) + }) + }) + return fmt.Sprintf("%v", left) +} + +func (layer Layer) Exclude(pkgPaths ...string) Layer { + patterns := lo.Map(pkgPaths, func(path string, _ int) *regexp.Regexp { reg, err := internal.PkgPattern(path) if err != nil { log.Fatal(err) } return reg }) - layer.B = lo.Filter(layer.B, func(pkg *internal.Package, _ int) bool { + return lo.Filter(layer, func(pkg *internal.Package, _ int) bool { return lo.NoneBy(patterns, func(pattern *regexp.Regexp) bool { return pattern.MatchString(pkg.ID()) }) }) - return layer -} - -func (layer Layer) Package(path string) Package { - panic("to be implemented") } func (layer Layer) Sub(name string, paths ...string) Layer { @@ -151,23 +123,22 @@ func (layer Layer) Sub(name string, paths ...string) Layer { } return reg }) - return Layer{A: fmt.Sprintf("%s-%s", layer.A, name), - B: lo.Filter(layer.B, func(pkg *internal.Package, _ int) bool { - return lo.SomeBy(patterns, func(pattern *regexp.Regexp) bool { - return pattern.MatchString(pkg.ID()) - }) - })} + return lo.Filter(layer, func(pkg *internal.Package, _ int) bool { + return lo.SomeBy(patterns, func(pattern *regexp.Regexp) bool { + return pattern.MatchString(pkg.ID()) + }) + }) } -func (layer Layer) Packages() []string { - return lo.Map(layer.B, func(item *internal.Package, _ int) string { - return item.ID() +func (layer Layer) packages() []string { + return lo.Map(layer, func(item *internal.Package, _ int) string { + return item.Path() }) } func (layer Layer) Imports() []string { var imports []string - for _, pkg := range layer.B { + for _, pkg := range layer { imports = append(imports, pkg.Imports()...) } return imports @@ -176,37 +147,37 @@ func (layer Layer) Imports() []string { func (layer Layer) ShouldNotReferLayers(layers ...Layer) error { var packages []string for _, l := range layers { - packages = append(packages, l.Packages()...) + packages = append(packages, l.packages()...) } path, ok := lo.Find(layer.Imports(), func(ref string) bool { return lo.Contains(packages, ref) }) - return lo.If(ok, fmt.Errorf("%s refers %s", layer.A, path)).Else(nil) + return lo.If(ok, fmt.Errorf("%s refers %s", layer.Name(), path)).Else(nil) } func (layer Layer) ShouldNotReferPackages(paths ...string) error { - return layer.ShouldNotReferLayers(Packages("_", paths)) + return layer.ShouldNotReferLayers(Lay(paths...)) } func (layer Layer) ShouldOnlyReferLayers(layers ...Layer) error { var pkgs []string for _, l := range layers { - pkgs = append(pkgs, l.Packages()...) + pkgs = append(pkgs, l.packages()...) } ref, ok := lo.Find(layer.Imports(), func(ref string) bool { return !lo.Contains(pkgs, ref) }) - return lo.If(ok, fmt.Errorf("%s refers %s", layer.A, ref)).Else(nil) + return lo.If(ok, fmt.Errorf("%s refers %s", layer.Name(), ref)).Else(nil) } func (layer Layer) ShouldOnlyReferPackages(paths ...string) error { - return layer.ShouldOnlyReferLayers(Packages("tempLayer", paths)) + return layer.ShouldOnlyReferLayers(Lay(paths...)) } func (layer Layer) ShouldBeOnlyReferredByLayers(layers ...Layer) error { var pkgs []*internal.Package for _, l := range layers { - pkgs = append(pkgs, l.B...) + pkgs = append(pkgs, l...) } others := lo.Filter(internal.Arch().Packages(), func(pkg1 *internal.Package, _ int) bool { return lo.NoneBy(pkgs, func(pkg2 *internal.Package) bool { @@ -218,18 +189,18 @@ func (layer Layer) ShouldBeOnlyReferredByLayers(layers ...Layer) error { return lo.Contains(layer.Imports(), ref) }) }); ok { - return fmt.Errorf("package %s refer layer %s", p.ID(), layer.A) + return fmt.Errorf("package %s refer layer %s", p.ID(), layer.Name()) } return nil } func (layer Layer) ShouldBeOnlyReferredByPackages(paths ...string) error { - layer1 := Packages("pkgLayer", paths) + layer1 := Lay(paths...) return layer.ShouldBeOnlyReferredByLayers(layer1) } func (layer Layer) DepthShouldLessThan(depth int) error { - pkg := lo.MaxBy(layer.B, func(a *internal.Package, b *internal.Package) bool { + pkg := lo.MaxBy(layer, func(a *internal.Package, b *internal.Package) bool { return len(strings.Split(a.ID(), "/")) > len(strings.Split(a.ID(), "/")) }) if acc := len(strings.Split(pkg.ID(), "/")); acc >= depth { @@ -243,7 +214,7 @@ func (layer Layer) Types() Types { } func (layer Layer) Files() Files { - return lo.Map(layer.B, func(pkg *internal.Package, _ int) File { + return lo.Map(layer, func(pkg *internal.Package, _ int) File { return File{A: pkg.ID(), B: pkg.GoFiles()} }) } @@ -256,7 +227,7 @@ func (layer Layer) FilesInPackages(paths ...string) Files { } return reg }) - return lo.FilterMap(layer.B, func(pkg *internal.Package, _ int) (File, bool) { + return lo.FilterMap(layer, func(pkg *internal.Package, _ int) (File, bool) { if lo.SomeBy(patterns, func(reg *regexp.Regexp) bool { return reg.MatchString(pkg.ID()) }) { diff --git a/layer_test.go b/layer_test.go index 20e545f..0a5169b 100644 --- a/layer_test.go +++ b/layer_test.go @@ -1,7 +1,6 @@ package archunit import ( - "github.com/samber/lo" "github.com/stretchr/testify/assert" "strings" "testing" @@ -18,10 +17,10 @@ func TestPackages(t *testing.T) { { name: "sample only", paths: []string{".../internal/sample"}, - size1: 0, + size1: 1, }, { - name: "sample and sub Packages", + name: "sample and sub Lay", paths: []string{".../internal/sample/..."}, except: []string{".../ext"}, size1: 12, @@ -40,11 +39,11 @@ func TestPackages(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - layer := Packages(test.name, test.paths) - assert.Equal(t, test.size1, len(layer.Packages())) + layer := Lay(test.paths...) + assert.Equal(t, test.size1, len(layer.packages())) if len(test.except) > 0 { layer = layer.Exclude(test.except...) - assert.Equal(t, test.size2, len(layer.Packages())) + assert.Equal(t, test.size2, len(layer.packages())) } }) } @@ -75,48 +74,16 @@ func TestLayer_Sub(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - layer := Packages(tt.name, tt.paths) - assert.Equal(t, tt.size1, len(layer.Packages())) + layer := Lay(tt.paths...) + assert.Equal(t, tt.size1, len(layer.packages())) layer = layer.Sub(tt.name, tt.sub...) - assert.Equal(t, tt.size2, len(layer.Packages())) + assert.Equal(t, tt.size2, len(layer.packages())) }) } } -func TestAllMethodOfTypeShouldInSameFile(t *testing.T) { - err := MethodsOfTypeShouldBeDefinedInSameFile() - assert.Errorf(t, err, "%s", err.Error()) - assert.True(t, strings.Contains(err.Error(), "internal/sample/service.UserService")) - assert.True(t, strings.Contains(err.Error(), "user_service.go")) - assert.True(t, strings.Contains(err.Error(), "user_service_ext.go")) -} - -func TestTypeImplement(t *testing.T) { - types := lo.Map(TypeImplement("internal/sample/service.NameService"), func(item Type, _ int) string { - return item.name - }) - assert.Equal(t, 2, len(types)) - assert.True(t, lo.Every([]string{ - "github.com/kcmvp/archunit/internal/sample/service.NameServiceImpl", - "github.com/kcmvp/archunit/internal/sample/service.FullNameImpl", - }, types)) -} - -func TestPackageNameShouldBeSameAsFolderName(t *testing.T) { - err := PackageNameShouldBeSameAsFolderName() - assert.Error(t, err) - assert.True(t, strings.Contains(err.Error(), "github.com/kcmvp/archunit/internal/sample/views's name is view")) -} - -func TestPackageNameShould(t *testing.T) { - err := PackageNameShouldBe(LowerCase) - assert.NoError(t, err) - err = PackageNameShouldBe(UpperCase) - assert.Error(t, err) -} - func TestSourceNameShould(t *testing.T) { - err := SourceNameShouldBe(LowerCase) + err := SourceNameShould(BeLowerCase) assert.Error(t, err) assert.True(t, strings.Contains(err.Error(), "internal/sample/views/UserView.go's name breaks the rule")) } @@ -128,20 +95,24 @@ func TestConstantsShouldBeDefinedInOneFileByPackage(t *testing.T) { } func TestLayPackages(t *testing.T) { - layer := Packages("controllerLayer", []string{"sample/controller", "sample/controller/..."}) + layer := Lay("sample/controller", "sample/controller/...") assert.ElementsMatch(t, []string{"github.com/kcmvp/archunit/internal/sample/controller", - "github.com/kcmvp/archunit/internal/sample/controller/module1"}, layer.Packages()) - assert.ElementsMatch(t, []string{"github.com/kcmvp/archunit/internal/sample/service", - "github.com/kcmvp/archunit/internal/sample/views", - "github.com/kcmvp/archunit/internal/sample/repository", - "github.com/kcmvp/archunit/internal/sample/service/ext/v1"}, layer.Imports()) + "github.com/kcmvp/archunit/internal/sample/controller/module1"}, layer.packages()) + assert.ElementsMatch(t, layer.Imports(), + []string{ + "github.com/kcmvp/archunit/internal/sample/service", + "github.com/kcmvp/archunit/internal/sample/views", + "github.com/gin-gonic/gin", + "net/http", + "github.com/kcmvp/archunit/internal/sample/repository", + "github.com/kcmvp/archunit/internal/sample/service/ext/v1"}) } func TestLayer_Refer(t *testing.T) { - controller := Packages("controller", []string{"sample/controller", "sample/controller/..."}) - model := Packages("model", []string{"sample/model"}) - service := Packages("service", []string{"sample/service", "sample/service/..."}) - repository := Packages("repository", []string{"sample/repository", "sample/repository/..."}) + controller := Lay("sample/controller", "sample/controller/...") + model := Lay("sample/model") + service := Lay("sample/service", "sample/service/...") + repository := Lay("sample/repository", "sample/repository/...") assert.NoError(t, controller.ShouldNotReferLayers(model)) assert.NoError(t, controller.ShouldNotReferPackages("sample/model")) assert.Errorf(t, controller.ShouldNotReferLayers(repository), "controller should not refer repository") diff --git a/package.go b/package.go index d5d16b8..c09716c 100644 --- a/package.go +++ b/package.go @@ -1,14 +1,55 @@ // nolint package archunit -import "github.com/samber/lo" +import ( + "fmt" + "github.com/kcmvp/archunit/internal" + "github.com/samber/lo" + "strings" +) -type Package lo.Tuple2[string, string] +type Packages []*internal.Package -func (pkg Package) ShouldNotRefer(path ...string) error { +func AllPackages() Packages { + return internal.Arch().Packages() +} + +func (pkgs Packages) Paths() []string { + return lo.Map(pkgs, func(pkg *internal.Package, _ int) string { + return pkg.Path() + }) +} + +func (pkgs Packages) Skip(paths ...string) Packages { + return lo.Filter(pkgs, func(pkg *internal.Package, _ int) bool { + return !lo.ContainsBy(paths, func(path string) bool { + return strings.HasSuffix(pkg.Path(), path) + }) + }) +} + +func (pkgs Packages) NameShouldBeSameAsFolder() error { + result := lo.FilterMap(pkgs, func(pkg *internal.Package, _ int) (string, bool) { + return pkg.Path(), !strings.HasSuffix(pkg.Path(), pkg.Name()) + }) + return lo.If(len(result) > 0, fmt.Errorf("package name and folder not the same: %v", pkgs.Paths())).Else(nil) +} + +func (pkgs Packages) NameShould(pattern NamePattern, args ...string) error { + if pkg, ok := lo.Find(pkgs, func(pkg *internal.Package) bool { + return !pattern(pkg.Name(), lo.If(args == nil, "").ElseF(func() string { + return args[0] + })) + }); ok { + return fmt.Errorf("package %s's name is %s", pkg.ID(), pkg.Name()) + } + return nil +} + +func (pkgs Packages) ShouldNotRefer(pkgPaths ...string) error { panic("implement me") } -func (pkg Package) ShouldBeOnlyReferredBy(path ...string) error { +func (pkgs Packages) ShouldBeOnlyReferredBy(pkgPaths ...string) error { panic("implement me") } diff --git a/package_test.go b/package_test.go new file mode 100644 index 0000000..e7b65b8 --- /dev/null +++ b/package_test.go @@ -0,0 +1,28 @@ +package archunit + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestPackages_NameShouldBeSameAsFolder(t *testing.T) { + pkgs := AllPackages() + assert.Equal(t, 15, len(pkgs)) + err := pkgs.NameShouldBeSameAsFolder() + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "archunit/internal/sample/views")) + assert.True(t, strings.Contains(err.Error(), "archunit/internal/sample/service/thirdparty")) + assert.True(t, strings.Contains(err.Error(), "archunit/internal/sample")) + pkgs = pkgs.Skip("internal/sample/views", "sample/service/thirdparty", "archunit/internal/sample") + assert.Equal(t, 12, len(pkgs)) + err = pkgs.NameShouldBeSameAsFolder() + assert.NoError(t, err) +} + +func TestPackageNameShould(t *testing.T) { + err := AllPackages().NameShould(BeLowerCase) + assert.NoError(t, err) + err = AllPackages().NameShould((BeUpperCase)) + assert.Error(t, err) +} diff --git a/type.go b/type.go index eaa03f4..e537756 100644 --- a/type.go +++ b/type.go @@ -1,45 +1,99 @@ // nolint package archunit -type Type struct { - name string - functions []Function -} +import ( + "fmt" + "github.com/kcmvp/archunit/internal" + "github.com/samber/lo" + lop "github.com/samber/lo/parallel" + "go/types" + "log" + "strings" + "sync" +) + +type Types []internal.Type -type Types []Type +func AllTypes() Types { + var typs Types + for _, pkg := range internal.Arch().Packages() { + typs = append(typs, pkg.Types()...) + } + return typs +} -func (types Type) Exclude(names ...string) Types { +func TypesEmbeddedWith(embeds ...string) Types { panic("to be implemented") } -func (types Types) EmbeddedWith(embeds ...string) Types { +// TypesImplement return all the types implement the interface +func TypesImplement(interName string) Types { + interType, ok := internal.Arch().Type(interName) + if !ok || !interType.Interface() { + log.Fatalf("can not find interface %s", interName) + } + var typMap sync.Map + lop.ForEach(internal.Arch().Packages(), func(pkg *internal.Package, index int) { + if strings.HasPrefix(pkg.ID(), internal.Arch().Module()) && + (pkg.ID() == interType.Package() || lo.Contains(pkg.Imports(), interType.Package())) { + implementations := lo.Filter(pkg.Types(), func(typ internal.Type, _ int) bool { + return !strings.HasSuffix(typ.Name(), interName) && types.Implements(typ.Raw(), interType.Raw().Underlying().(*types.Interface)) + }) + if len(implementations) > 0 { + typMap.Store(pkg.ID(), implementations) + } + } + }) + var typs Types + typMap.Range(func(_, value any) bool { + typs = append(typs, value.([]internal.Type)...) + return true + }) + return typs +} + +func (typs Types) Skip(names ...string) Types { panic("to be implemented") } -func (types Types) Implement(inters ...string) Types { +func (typs Types) EmbeddedWith(embeds ...string) Types { panic("to be implemented") } -func (types Types) InPackage(paths ...string) Types { +func (typs Types) Implement(inters ...string) Types { panic("to be implemented") } -func (types Types) Functions() []Function { +func (typs Types) InPackage(paths ...string) Types { panic("to be implemented") } -func (types Types) ShouldBePrivate() error { +func (typs Types) Functions() []Functions { panic("to be implemented") } -func (functions Types) ShouldBePublic() error { +func (typs Types) ShouldBeDefinedInSameFile() error { + for _, pkg := range internal.Arch().Packages() { + for _, typ := range pkg.Types() { + files := lo.Uniq(lo.Map(typ.Methods(), func(f internal.Function, _ int) string { + return f.GoFile() + })) + if len(files) > 1 { + return fmt.Errorf("methods of type %s are defined in files %v", typ.Name(), files) + } + } + } + return nil +} + +func (typs Types) ShouldBe(visibility Visibility) error { panic("to be implemented") } -func (types Types) ShouldBeInPackages(pkgs ...string) error { +func (typs Types) ShouldBeInPackages(pkgs ...string) error { panic("to be implemented") } -func (types Types) NameShould(pattern NamePattern) error { +func (typs Types) NameShould(pattern NamePattern) error { panic("to be implemented") } diff --git a/type_test.go b/type_test.go new file mode 100644 index 0000000..58c45d8 --- /dev/null +++ b/type_test.go @@ -0,0 +1,79 @@ +package archunit + +import ( + "github.com/kcmvp/archunit/internal" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAllTypes(t *testing.T) { + typs := lo.Map(AllTypes(), func(item internal.Type, _ int) string { + return item.Name() + }) + expected := []string{ + "github.com/kcmvp/archunit/internal.Artifact", + "github.com/kcmvp/archunit/internal.Function", + "github.com/kcmvp/archunit/internal.Package", + "github.com/kcmvp/archunit/internal.Param", + "github.com/kcmvp/archunit/internal.ParseMode", + "github.com/kcmvp/archunit/internal.Type", + "github.com/kcmvp/archunit/internal.Variable", + "github.com/kcmvp/archunit/internal/sample/service/ext/v1.LoginService", + "github.com/kcmvp/archunit/internal/sample/controller/module1.AppController", + "github.com/kcmvp/archunit/internal/sample/service/ext.Cross", + "github.com/kcmvp/archunit/internal/sample/model.User", + "github.com/kcmvp/archunit/internal/sample/vutil.ViewUtil", + "github.com/kcmvp/archunit.File", + "github.com/kcmvp/archunit.Files", + "github.com/kcmvp/archunit.Functions", + "github.com/kcmvp/archunit.Layer", + "github.com/kcmvp/archunit.NamePattern", + "github.com/kcmvp/archunit.Packages", + "github.com/kcmvp/archunit.Types", + "github.com/kcmvp/archunit.Visibility", + "github.com/kcmvp/archunit/internal/sample/views.UserView", + "github.com/kcmvp/archunit/internal/sample/controller.LoginController", + "github.com/kcmvp/archunit/internal/sample/service.Audit", + "github.com/kcmvp/archunit/internal/sample/service.FullNameImpl", + "github.com/kcmvp/archunit/internal/sample/service.NameService", + "github.com/kcmvp/archunit/internal/sample/service.NameServiceImpl", + "github.com/kcmvp/archunit/internal/sample/service.UserService", + "github.com/kcmvp/archunit/internal/sample/service/thirdparty.S3", + "github.com/kcmvp/archunit/internal/sample/repository/ext.UserRepositoryExt", + "github.com/kcmvp/archunit/internal/sample/service/ext/v2.LoginService", + "github.com/kcmvp/archunit/internal/sample/repository.FF", + "github.com/kcmvp/archunit/internal/sample/repository.UserRepository", + "github.com/kcmvp/archunit/internal/sample/controller.MyRouterGroup", + } + assert.ElementsMatch(t, expected, typs) +} + +func TestTypeImplement(t *testing.T) { + tests := []struct { + interType string + implementation []string + }{ + { + interType: "internal/sample/service.NameService", + implementation: []string{ + "github.com/kcmvp/archunit/internal/sample/service.NameServiceImpl", + "github.com/kcmvp/archunit/internal/sample/service.FullNameImpl", + }, + }, + { + interType: "github.com/gin-gonic/gin.IRouter", + implementation: []string{ + "github.com/kcmvp/archunit/internal/sample/controller.MyRouterGroup", + }, + }, + } + for _, test := range tests { + t.Run(test.interType, func(t *testing.T) { + types := lo.Map(TypesImplement(test.interType), func(item internal.Type, _ int) string { + return item.Name() + }) + assert.ElementsMatch(t, test.implementation, types) + }) + } +}