From cb13e284ffc4cff46f4be94024f6af990892b6e0 Mon Sep 17 00:00:00 2001 From: James Harris Date: Sat, 17 Aug 2024 09:52:59 +1000 Subject: [PATCH] Add `static.FromDir()`. --- CHANGELOG.md | 10 ++++- static/adaptor_test.go | 10 +---- static/app_test.go | 79 +++++------------------------------- static/constructor_test.go | 10 +---- static/handler_test.go | 82 +++++--------------------------------- static/ident_test.go | 28 ++----------- static/invalid_test.go | 2 +- static/static.go | 24 +++++++++-- 8 files changed, 53 insertions(+), 192 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c3a3b..efde1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,17 @@ The format is based on [Keep a Changelog], and this project adheres to ## [0.13.5] - 2024-08-16 +### Added + +- Added `static.PackagesLoadMode` as a convenience for using Go's built-in + `packages.Load()` with a mode suitable for static analysis. +- Added `static.FromDir()` as a convenience for analyzing packages recursively + from a directory. + ### Fixed -- Fixed panicking when handling type aliases in static analysis. +- Handle alias types under Go v1.23 (or when using `GODEBUG=gotypealias=1`), which + previously caused a panic. ## [0.13.4] - 2024-08-16 diff --git a/static/adaptor_test.go b/static/adaptor_test.go index feda7dc..d9ea82b 100644 --- a/static/adaptor_test.go +++ b/static/adaptor_test.go @@ -7,20 +7,12 @@ import ( . "github.com/dogmatiq/configkit/static" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "golang.org/x/tools/go/packages" ) var _ = Describe("func FromPackages() (adaptor function)", func() { When("the the handler is created by adapting a partial handler implementation", func() { It("builds the configuration from the adapted type", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/adaptor", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/adaptor") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) diff --git a/static/app_test.go b/static/app_test.go index ebd60b9..198cab5 100644 --- a/static/app_test.go +++ b/static/app_test.go @@ -7,20 +7,12 @@ import ( . "github.com/dogmatiq/configkit/static" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "golang.org/x/tools/go/packages" ) var _ = Describe("func FromPackages() (application detection)", func() { When("a package contains a single application", func() { It("returns the application configuration", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/single-app", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/apps/single-app") Expect(apps).To(HaveLen(1)) Expect(apps[0].Identity()).To( @@ -44,14 +36,7 @@ var _ = Describe("func FromPackages() (application detection)", func() { When("multiple packages contain applications", func() { It("returns all of the application configurations", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/multiple-apps-in-pkgs", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/apps/multiple-apps-in-pkgs") Expect(apps).To(HaveLen(2)) Expect( @@ -76,14 +61,7 @@ var _ = Describe("func FromPackages() (application detection)", func() { When("a single package contains multiple applications", func() { It("returns all of the application configurations", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/multiple-apps-in-single-pkg/apps", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/apps/multiple-apps-in-single-pkg/apps") Expect(apps).To(HaveLen(2)) Expect( @@ -108,16 +86,9 @@ var _ = Describe("func FromPackages() (application detection)", func() { When("a package contains an application implemented with pointer receivers", func() { It("returns the application configuration", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/pointer-receiver-app", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) - + apps := FromDir("testdata/apps/pointer-receiver-app") Expect(apps).To(HaveLen(1)) + Expect(apps[0].Identity()).To( Equal( configkit.Identity{ @@ -131,30 +102,16 @@ var _ = Describe("func FromPackages() (application detection)", func() { When("none of the packages contain any applications", func() { It("returns an empty slice", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/no-app", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) - + apps := FromDir("testdata/apps/no-app") Expect(apps).To(BeEmpty()) }) }) When("a field within the application type is registered as a handler", func() { It("includes the handler in the application configuration", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/handler-from-field", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/apps/handler-from-field") Expect(apps).To(HaveLen(1)) + Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) a := apps[0].Handlers().Aggregates()[0] @@ -171,14 +128,7 @@ var _ = Describe("func FromPackages() (application detection)", func() { When("an application in the package has multiple handlers", func() { It("returns all messages consumed or produced by all handlers", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/apps/app-level-messages", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/apps/app-level-messages") Expect(apps).To(HaveLen(1)) Expect(apps[0].MessageNames()).To(Equal( @@ -200,14 +150,3 @@ var _ = Describe("func FromPackages() (application detection)", func() { }) }) }) - -func loadPackages(cfg packages.Config) []*packages.Package { - pkgs, err := packages.Load(&cfg, "./...") - Expect(err).NotTo(HaveOccurred()) - - for _, pkg := range pkgs { - ExpectWithOffset(1, pkg.Errors).To(BeEmpty()) - } - - return pkgs -} diff --git a/static/constructor_test.go b/static/constructor_test.go index 7ef6d5c..e6b7b63 100644 --- a/static/constructor_test.go +++ b/static/constructor_test.go @@ -7,20 +7,12 @@ import ( . "github.com/dogmatiq/configkit/static" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "golang.org/x/tools/go/packages" ) var _ = Describe("func FromPackages() (constructor function)", func() { When("the handler is created by a call to a 'constructor' function", func() { It("builds the configuration from the adapted type", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/constructor", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/constructor") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) diff --git a/static/handler_test.go b/static/handler_test.go index ac964d7..d20b28a 100644 --- a/static/handler_test.go +++ b/static/handler_test.go @@ -9,7 +9,6 @@ import ( . "github.com/dogmatiq/configkit/static" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "golang.org/x/tools/go/packages" ) // matchIdentities matches the given identities to those of the handlers in the @@ -27,14 +26,7 @@ func matchIdentities( var _ = Describe("func FromPackages() (handler analysis)", func() { When("the application contains a single handler of each type", func() { It("returns a single configuration for each handler type", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/single", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/single") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -176,14 +168,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { }) It("returns a single configuration for each handler type", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/typealias", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/typealias") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -311,14 +296,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("messages are passed to the *Configurer.Routes() method", func() { It("includes messages passed as args to *Configurer.Routes() method only", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/only-routes-args", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/only-routes-args") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -446,14 +424,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("messages are passed to the *Configurer.Routes() method as a dynamically populated splice", func() { It("returns a single configuration for each handler type", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/dynamic-routes", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/dynamic-routes") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -581,14 +552,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("messages are passed to the *Configurer.Routes() method in conditional branches", func() { It("returns messages populated in every conditional branch", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/conditional-branches", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/conditional-branches") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -716,14 +680,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("nil is passed to a call of *Configurer.Routes() methods", func() { It("does not populate messages", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/nil-routes", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/nil-routes") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) @@ -827,14 +784,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("the application multiple handlers of each type", func() { It("returns all of the handler configurations", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/multiple", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/multiple") Expect(apps).To(HaveLen(1)) matchIdentities( @@ -877,14 +827,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("a nil value is passed as a handler", func() { It("does not add a handler to the application configuration", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/nil-handler", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/nil-handler") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers()).To(Equal(configkit.HandlerSet{})) }) @@ -892,14 +835,7 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { When("a handler with a non-pointer methodset is registered as a pointer", func() { It("includes the handler in the application configuration", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/handlers/pointer-handler-with-non-pointer-methodset", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/handlers/pointer-handler-with-non-pointer-methodset") Expect(apps).To(HaveLen(1)) Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) diff --git a/static/ident_test.go b/static/ident_test.go index f9c86ce..e098319 100644 --- a/static/ident_test.go +++ b/static/ident_test.go @@ -6,20 +6,12 @@ import ( . "github.com/dogmatiq/configkit/static" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "golang.org/x/tools/go/packages" ) var _ = Describe("func FromPackages() (application identity)", func() { When("the identity is specified with non-literal constants", func() { It("uses the values from the constants", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/ident/const-value-ident", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/ident/const-value-ident") Expect(apps).To(HaveLen(1)) Expect(apps[0].Identity()).To( @@ -43,14 +35,7 @@ var _ = Describe("func FromPackages() (application identity)", func() { When("the identity is specified with string literals", func() { It("uses the literal values", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/ident/literal-value-ident", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/ident/literal-value-ident") Expect(apps).To(HaveLen(1)) Expect(apps[0].Identity()).To( @@ -74,14 +59,7 @@ var _ = Describe("func FromPackages() (application identity)", func() { When("the identity is specified with non-constant expressions", func() { It("uses a zero-value identity", func() { - cfg := packages.Config{ - Mode: LoadPackagesConfigMode, - Dir: "testdata/ident/variable-value-ident", - } - - pkgs := loadPackages(cfg) - - apps := FromPackages(pkgs) + apps := FromDir("testdata/ident/variable-value-ident") Expect(apps).To(HaveLen(1)) Expect(apps[0].Identity()).To( diff --git a/static/invalid_test.go b/static/invalid_test.go index cb978cd..d4e4ff1 100644 --- a/static/invalid_test.go +++ b/static/invalid_test.go @@ -11,7 +11,7 @@ var _ = Describe("func FromPackages() (unbuildable packages)", func() { When("a package contains a file with invalid Go syntax", func() { It("ignores the package", func() { cfg := packages.Config{ - Mode: LoadPackagesConfigMode, + Mode: PackagesLoadMode, Dir: "testdata/invalid/invalid-syntax", } diff --git a/static/static.go b/static/static.go index f8d1d87..ee33484 100644 --- a/static/static.go +++ b/static/static.go @@ -9,17 +9,33 @@ import ( "golang.org/x/tools/go/ssa/ssautil" ) -// LoadPackagesConfigMode is the set of load mode values required to obtain the -// information necessary to statically analyze Dogma applications. -const LoadPackagesConfigMode = packages.NeedFiles | +// PackagesLoadMode is the minimal [packages.LoadMode] required when loading +// packages for analysis by [FromPackages]. +const PackagesLoadMode = packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | - packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps +// FromDir returns the configurations of the Dogma applications implemented +// within the packages in the given directory and its children. +func FromDir(dir string) []configkit.Application { + pkgs, err := packages.Load( + &packages.Config{ + Mode: PackagesLoadMode, + Dir: dir, + }, + "./...", + ) + if err != nil { + panic(err) + } + + return FromPackages(pkgs) +} + // FromPackages returns the configurations of the Dogma applications implemented // within a set of packages. //