diff --git a/.gitignore b/.gitignore index 22ef70a6..118bda4d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ docs/ve ve .cache coverage.txt -site/ \ No newline at end of file +site/ +.task/ \ No newline at end of file diff --git a/.mockery.yaml b/.mockery.yaml index 33e0f095..690fbf0d 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -23,6 +23,3 @@ packages: - mockname: RequesterVariadic Expecter: RequesterReturnElided: - - - \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 9b589994..ffe75897 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -4,22 +4,44 @@ tasks: test: cmds: - go test -v -coverprofile=coverage.txt ./... + desc: run unit tests + sources: + - "**/*.go" + generates: + - coverage.txt + + coverage: + deps: [test] + desc: run unit tests and create HTML coverage file + cmds: + - go tool cover -html=coverage.txt fmt: + desc: auto-format all go files + sources: + - "**/*.go" cmds: - go fmt ./... mocks: + desc: generate mockery mocks cmds: - go run . docker: + desc: build the mockery docker image cmds: - docker build -t vektra/mockery . lint: + desc: run all the defined linters + sources: + - "**/*.go" cmds: - go run github.com/golangci/golangci-lint/cmd/golangci-lint run test.ci: - deps: [test, fmt, mocks, lint] \ No newline at end of file + deps: [test, fmt, mocks, lint] + + default: + deps: [test.ci] \ No newline at end of file diff --git a/cmd/mockery.go b/cmd/mockery.go index 8a2d09c7..22cc8d59 100644 --- a/cmd/mockery.go +++ b/cmd/mockery.go @@ -208,6 +208,10 @@ func (r *RootApp) Run() error { log.Info().Msgf("Using config: %s", r.Config.Config) ctx := log.WithContext(context.Background()) + if err := r.Config.Initialize(ctx); err != nil { + return err + } + if r.Config.Version { fmt.Println(logging.GetSemverInfo()) return nil @@ -290,7 +294,7 @@ func (r *RootApp) Run() error { } else if (r.Config.FileName != "" || r.Config.StructName != "") && r.Config.All { log.Fatal().Msgf("Cannot specify --filename or --structname with --all") } else if r.Config.Dir != "" && r.Config.Dir != "." && r.Config.SrcPkg != "" { - log.Fatal().Msgf("Specify -dir or -srcpkg, but not both") + log.Fatal().Msgf("Specify --dir or --srcpkg, but not both") } else if r.Config.Name != "" { recursive = r.Config.Recursive if strings.ContainsAny(r.Config.Name, regexMetadataChars) { diff --git a/cmd/showconfig.go b/cmd/showconfig.go index 72359bb8..25c00580 100644 --- a/cmd/showconfig.go +++ b/cmd/showconfig.go @@ -1,10 +1,14 @@ package cmd import ( + "context" "fmt" + "io" + "os" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/vektra/mockery/v2/pkg/config" "github.com/vektra/mockery/v2/pkg/logging" "gopkg.in/yaml.v2" @@ -13,28 +17,43 @@ import ( func NewShowConfigCmd() *cobra.Command { return &cobra.Command{ Use: "showconfig", - Short: "Show the merged config", - Long: `Print out a yaml representation of the merged config. - This initializes viper and prints out the merged configuration between - config files, environment variables, and CLI flags.`, - RunE: func(cmd *cobra.Command, args []string) error { - - config := &config.Config{} - if err := viperCfg.UnmarshalExact(config); err != nil { - return errors.Wrapf(err, "failed to unmarshal config") - } - out, err := yaml.Marshal(config) - if err != nil { - return errors.Wrapf(err, "Failed to marshal yaml") - } - log, err := logging.GetLogger(config.LogLevel) - if err != nil { - panic(err) - } - log.Info().Msgf("Using config: %s", config.Config) + Short: "Show the yaml config", + Long: `Print out a yaml representation of the yaml config file. This does not show config from exterior sources like CLI, environment etc.`, + RunE: func(cmd *cobra.Command, args []string) error { return showConfig(cmd, args, viperCfg, os.Stdout) }, + } +} - fmt.Printf("%s", string(out)) - return nil - }, +func showConfig( + cmd *cobra.Command, + args []string, + v *viper.Viper, + outputter io.Writer, +) error { + if v == nil { + v = viperCfg + } + ctx := context.Background() + config, err := config.NewConfigFromViper(v) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal config") + } + if err := config.Initialize(ctx); err != nil { + return err } + cfgMap, err := config.CfgAsMap(ctx) + if err != nil { + panic(err) + } + out, err := yaml.Marshal(cfgMap) + if err != nil { + return errors.Wrapf(err, "Failed to marshal yaml") + } + log, err := logging.GetLogger(config.LogLevel) + if err != nil { + panic(err) + } + log.Info().Msgf("Using config: %s", config.Config) + + fmt.Fprintf(outputter, "%s", string(out)) + return nil } diff --git a/cmd/showconfig_test.go b/cmd/showconfig_test.go index 3f896aa5..5110a4a6 100644 --- a/cmd/showconfig_test.go +++ b/cmd/showconfig_test.go @@ -1,8 +1,11 @@ package cmd import ( + "bytes" "testing" + "github.com/chigopher/pathlib" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -10,3 +13,28 @@ func TestNewShowConfigCmd(t *testing.T) { cmd := NewShowConfigCmd() assert.Equal(t, "showconfig", cmd.Name()) } + +func TestShowCfg(t *testing.T) { + v := viper.New() + v.Set("with-expecter", true) + cfgFile := pathlib.NewPath(t.TempDir()).Join("config.yaml") + err := cfgFile.WriteFile([]byte(` +with-expecter: true +all: true +packages: + github.com/vektra/mockery/v2/pkg: + config: + all: true`)) + assert.NoError(t, err) + v.Set("config", cfgFile.String()) + buf := new(bytes.Buffer) + assert.NoError(t, showConfig(nil, nil, v, buf)) + assert.Equal(t, `all: true +packages: + github.com/vektra/mockery/v2/pkg: + config: + all: true + with-expecter: true +with-expecter: true +`, buf.String()) +} diff --git a/go.mod b/go.mod index df29dba4..f64c6e84 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/vektra/mockery/v2 go 1.19 require ( - github.com/chigopher/pathlib v0.12.0 + github.com/chigopher/pathlib v0.13.0 + github.com/go-task/task/v3 v3.24.0 + github.com/golangci/golangci-lint v1.52.2 github.com/iancoleman/strcase v0.2.0 github.com/jinzhu/copier v0.3.5 github.com/mitchellh/go-homedir v1.1.0 @@ -29,7 +31,7 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/OpenPeeDeeP/depguard v1.1.1 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect @@ -58,7 +60,6 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/go-critic/go-critic v0.7.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/go-task/task/v3 v3.23.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.1.0 // indirect @@ -75,7 +76,6 @@ require ( github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect - github.com/golangci/golangci-lint v1.52.2 // indirect github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect github.com/golangci/misspell v0.4.0 // indirect diff --git a/go.sum b/go.sum index bf4356bd..2cde0542 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -73,6 +75,7 @@ github.com/ashanbrown/forbidigo v1.5.1 h1:WXhzLjOlnuDYPYQo/eFlcFMi8X/kLfvWLYu6CS github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -97,8 +100,8 @@ github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iy github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= -github.com/chigopher/pathlib v0.12.0 h1:1GM7fN/IwXXmOHbd1jkMqHD2wUhYqUvafgxTwmLT/q8= -github.com/chigopher/pathlib v0.12.0/go.mod h1:EJ5UtJ/sK8Nt6q3VWN+EwZLZ3g0afJiG8NegYiQQ/gQ= +github.com/chigopher/pathlib v0.13.0 h1:9AYqYGR+JaYJtZfTSsC+Wvz7CBd2jcZFb4fva7Z4r+4= +github.com/chigopher/pathlib v0.13.0/go.mod h1:EJ5UtJ/sK8Nt6q3VWN+EwZLZ3g0afJiG8NegYiQQ/gQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -108,6 +111,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= @@ -133,7 +137,7 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -149,11 +153,14 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/task/v3 v3.23.0 h1:egOOrya0biMnrV1W8RSEo2pviHMGTtBmy4VqxUYfcyk= github.com/go-task/task/v3 v3.23.0/go.mod h1:HdRk71gkYTKGu3e489AhuXkYYt/A6RvF8SiE9RKonVs= +github.com/go-task/task/v3 v3.24.0 h1:0DKsGTmqYbEaACffm9H2BOm8pTcuSnb5SRUl7TCilQ4= +github.com/go-task/task/v3 v3.24.0/go.mod h1:wx5IE8QSi35PEcTt0eC37C1tOTrSVIWBpNtiC6R/V0E= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= @@ -165,6 +172,7 @@ github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsO github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= @@ -275,6 +283,7 @@ github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3 github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -329,7 +338,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -355,6 +364,7 @@ github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1r github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -392,6 +402,7 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nishanths/exhaustive v0.9.5 h1:TzssWan6orBiLYVqewCG8faud9qlFntJE30ACpzmGME= github.com/nishanths/exhaustive v0.9.5/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -400,6 +411,9 @@ github.com/nunnatsa/ginkgolinter v0.9.0 h1:Sm0zX5QfjJzkeCjEp+t6d3Ha0jwvoDjleP9XC github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuqe5j4rW1gxJRmI= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -450,7 +464,7 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8 github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= @@ -519,7 +533,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -529,7 +542,9 @@ github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplB github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= @@ -569,6 +584,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -629,7 +645,6 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -675,6 +690,7 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -759,7 +775,6 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -856,7 +871,6 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= @@ -958,9 +972,9 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/pkg/config/config.go b/pkg/config/config.go index 22b89190..faff61d7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,6 +1,7 @@ package config import ( + "bufio" "context" "fmt" "os" @@ -14,24 +15,19 @@ import ( "github.com/rs/zerolog" "github.com/spf13/viper" "github.com/vektra/mockery/v2/pkg/logging" + "golang.org/x/tools/go/packages" "gopkg.in/yaml.v3" ) var ( ErrNoConfigFile = fmt.Errorf("no config file exists") + ErrPkgNotFound = fmt.Errorf("package not found in config") ) type Interface struct { Config Config `mapstructure:"config"` } -type Package struct { - Config Config `mapstructure:"config"` - Interfaces map[string]Interface `mapstructure:"interfaces"` -} - -type Packages map[string]Package - type Config struct { All bool `mapstructure:"all"` BuildTags string `mapstructure:"tags"` @@ -109,10 +105,23 @@ func NewConfigFromViper(v *viper.Viper) (*Config, error) { return c, nil } -// cfgAsMap reads in the config file and returns a map representation, instead of a +func (c *Config) Initialize(ctx context.Context) error { + log := zerolog.Ctx(ctx) + if err := c.discoverRecursivePackages(ctx); err != nil { + return fmt.Errorf("failed to discover recursive packages: %w", err) + } + + log.Trace().Msg("merging in config") + if err := c.mergeInConfig(ctx); err != nil { + return err + } + return nil +} + +// CfgAsMap reads in the config file and returns a map representation, instead of a // struct representation. This is mainly needed because viper throws away case-sensitivity // in the `packages` section, which won't work when defining interface names 😞 -func (c *Config) cfgAsMap(ctx context.Context) (map[string]any, error) { +func (c *Config) CfgAsMap(ctx context.Context) (map[string]any, error) { log := zerolog.Ctx(ctx) configPath := pathlib.NewPath(c.Config) @@ -155,7 +164,7 @@ func (c *Config) GetPackages(ctx context.Context) ([]string, error) { // so this breaks our logic. We need to manually parse this section // instead. See: https://github.com/spf13/viper/issues/819 log := zerolog.Ctx(ctx) - cfgMap, err := c.cfgAsMap(ctx) + cfgMap, err := c.CfgAsMap(ctx) if err != nil { return nil, err } @@ -178,16 +187,20 @@ func (c *Config) GetPackages(ctx context.Context) ([]string, error) { } func (c *Config) getPackageConfigMap(ctx context.Context, packageName string) (map[string]any, error) { - cfgMap, err := c.cfgAsMap(ctx) + cfgMap, err := c.CfgAsMap(ctx) if err != nil { return nil, err } packageSection := cfgMap["packages"].(map[string]any) configUnmerged, ok := packageSection[packageName] if !ok { - return nil, fmt.Errorf("package %s is not found in config", packageName) + return nil, ErrPkgNotFound + } + configAsMap, isMap := configUnmerged.(map[string]any) + if isMap { + return configAsMap, nil } - return configUnmerged.(map[string]any), nil + return map[string]any{}, nil } func (c *Config) GetPackageConfig(ctx context.Context, packageName string) (*Config, error) { @@ -244,7 +257,7 @@ func (c *Config) ShouldGenerateInterface(ctx context.Context, packageName, inter return false, err } - interfacesSection, err := c.getInterfacesSection(ctx, packageName, interfaceName) + interfacesSection, err := c.getInterfacesSection(ctx, packageName) if err != nil { return false, err } @@ -252,7 +265,7 @@ func (c *Config) ShouldGenerateInterface(ctx context.Context, packageName, inter return pkgConfig.All || interfaceExists, nil } -func (c *Config) getInterfacesSection(ctx context.Context, packageName string, interfaceName string) (map[string]any, error) { +func (c *Config) getInterfacesSection(ctx context.Context, packageName string) (map[string]any, error) { pkgMap, err := c.getPackageConfigMap(ctx, packageName) if err != nil { return nil, err @@ -278,7 +291,7 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int if err != nil { return nil, errors.Wrapf(err, "failed to get config for package when iterating over interface") } - interfacesSection, err := c.getInterfacesSection(ctx, packageName, interfaceName) + interfacesSection, err := c.getInterfacesSection(ctx, packageName) if err != nil { return nil, err } @@ -359,3 +372,304 @@ func (c *Config) GetInterfaceConfig(ctx context.Context, packageName string, int } return configs, nil } + +// addSubPkgConfig injects the given pkgPath into the `packages` config section. +// You specify a parentPkgPath to inherit the config from. +func (c *Config) addSubPkgConfig(ctx context.Context, subPkgPath string, parentPkgPath string) error { + log := zerolog.Ctx(ctx).With(). + Str("parent-package", parentPkgPath). + Str("sub-package", subPkgPath).Logger() + + log.Trace().Msg("adding sub-package to config map") + parentPkgConfig, err := c.getPackageConfigMap(ctx, parentPkgPath) + if err != nil { + log.Err(err). + Msg("failed to get package config for parent package") + return fmt.Errorf("failed to get package config: %w", err) + } + + log.Trace().Msg("getting config") + cfg, err := c.CfgAsMap(ctx) + if err != nil { + return fmt.Errorf("failed to get configuration map: %w", err) + } + + log.Trace().Msg("getting packages section") + packagesSection := cfg["packages"].(map[string]any) + + // Don't overwrite any config that already exists + _, pkgExists := packagesSection[subPkgPath] + if !pkgExists { + log.Trace().Msg("sub-package doesn't exist in config") + packagesSection[subPkgPath] = map[string]any{} + newPkgSection := packagesSection[subPkgPath].(map[string]any) + newPkgSection["config"] = parentPkgConfig["config"] + } else { + log.Trace().Msg("sub-package exists in config") + // The sub-package exists in config. Check if it has its + // own `config` section and merge with the parent package + // if so. + subPkgConfig, err := c.getPackageConfigMap(ctx, subPkgPath) + + if err != nil { + log.Err(err).Msg("could not get child package config") + return fmt.Errorf("failed to get sub-package config: %w", err) + } + + for key, val := range parentPkgConfig { + if _, keyInSubPkg := subPkgConfig[key]; !keyInSubPkg { + subPkgConfig[key] = val + } + + } + } + + return nil +} + +func (c *Config) subPackages(ctx context.Context, pkgPath string, pkgConfig *Config) ([]string, error) { + log := zerolog.Ctx(ctx) + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName | packages.NeedFiles, + }, pkgPath) + if err != nil { + return nil, fmt.Errorf("failed to load packages: %w", err) + } + pkg := pkgs[0] + + representativeFile := pathlib.NewPath(pkg.GoFiles[0]) + searchRoot := representativeFile.Parent() + packageRootName := pathlib.NewPath(pkg.PkgPath) + packageRootPath := searchRoot + subPackages := []string{} + + walker, err := pathlib.NewWalk( + searchRoot, + pathlib.WalkAlgorithm(pathlib.AlgorithmBasic), + pathlib.WalkFollowSymlinks(false), + pathlib.WalkVisitDirs(false), + pathlib.WalkVisitFiles(true), + ) + if err != nil { + return nil, fmt.Errorf("failed to create filesystem walker: %w", err) + } + + visitedDirs := map[string]any{} + subdirectoriesWithGoFiles := []*pathlib.Path{} + + // We consider the searchRoot to already be visited because + // we know it's already in the configuration. + visitedDirs[searchRoot.String()] = nil + + // Walk the filesystem path, starting at the root of the package we've + // been given. Note that this will always work because Golang downloads + // the package when we call `packages.Load` + walkErr := walker.Walk(func(path *pathlib.Path, info os.FileInfo, err error) error { + if err != nil { + return err + } + + file, err := path.OpenFile(os.O_RDONLY) + if err != nil { + return err + } + defer file.Close() + + _, haveVisitedDir := visitedDirs[path.Parent().String()] + if !haveVisitedDir && strings.HasSuffix(path.Name(), ".go") { + // Skip auto-generated files + scanner := bufio.NewScanner(file) + for scanner.Scan() { + text := scanner.Text() + if strings.Contains(text, "DO NOT EDIT") { + log.Debug().Stringer("path", path).Msg("skipping file as auto-generated") + return nil + } else if strings.HasPrefix(text, "package ") { + break + } + } + + l := log.With().Stringer("path", path.Parent()).Logger() + l.Debug().Msg("subdirectory has a .go file, adding this path to packages config") + subdirectoriesWithGoFiles = append(subdirectoriesWithGoFiles, path.Parent()) + visitedDirs[path.Parent().String()] = nil + } + return nil + }) + if walkErr != nil { + return nil, fmt.Errorf("error occured during filesystem walk: %w", err) + } + + // Parse the subdirectories we found into their respective fully qualified + // package paths + for _, d := range subdirectoriesWithGoFiles { + relativeFilesystemPath, err := d.RelativeTo(packageRootPath) + if err != nil { + log.Err(err).Stringer("root", packageRootPath).Stringer("subRoot", d).Msg("failed to make subroot relative to root") + return nil, fmt.Errorf("failed to make subroot relative to root: %w", err) + } + absolutePackageName := packageRootName.Join(relativeFilesystemPath.Parts()...) + subPackages = append(subPackages, absolutePackageName.String()) + } + + return subPackages, nil +} + +// discoverRecursivePackages parses the provided config for packages marked as +// recursive and recurses the file tree to find all sub-packages. +func (c *Config) discoverRecursivePackages(ctx context.Context) error { + log := zerolog.Ctx(ctx) + recursivePackages := map[string]*Config{} + packages, err := c.GetPackages(ctx) + if err != nil { + return fmt.Errorf("failed to get packages: %w", err) + } + for _, pkg := range packages { + pkgConfig, err := c.GetPackageConfig(ctx, pkg) + if err != nil { + return fmt.Errorf("failed to get package config: %w", err) + } + if pkgConfig.Recursive { + recursivePackages[pkg] = pkgConfig + } + } + if len(recursivePackages) == 0 { + return nil + } + for pkgPath, conf := range recursivePackages { + pkgLog := log.With().Str("package-name", pkgPath).Logger() + pkgCtx := pkgLog.WithContext(ctx) + pkgLog.Debug().Msg("discovering sub-packages") + subPkgs, err := c.subPackages(pkgCtx, pkgPath, conf) + if err != nil { + return fmt.Errorf("failed to get subpackages: %w", err) + } + for _, subPkg := range subPkgs { + subPkgLog := pkgLog.With().Str("sub-package", subPkg).Logger() + subPkgCtx := subPkgLog.WithContext(pkgCtx) + + subPkgLog.Debug().Msg("adding sub-package config") + if err := c.addSubPkgConfig(subPkgCtx, subPkg, pkgPath); err != nil { + subPkgLog.Err(err).Msg("failed to add sub-package config") + return fmt.Errorf("failed to add sub-package config: %w", err) + } + } + } + log.Trace().Msg("done discovering recursive packages") + + return nil + +} + +func contains[T comparable](slice []T, elem T) bool { + for _, element := range slice { + if elem == element { + return true + } + } + return false +} + +// mergeInConfig takes care of merging inheritable configuration +// in the config map. For example, it merges default config, then +// package-level config, then interface-level config. +func (c *Config) mergeInConfig(ctx context.Context) error { + log := zerolog.Ctx(ctx) + + log.Trace().Msg("getting packages") + pkgs, err := c.GetPackages(ctx) + if err != nil { + return err + } + + log.Trace().Msg("getting default config") + defaultCfg, err := c.CfgAsMap(ctx) + if err != nil { + return err + } + for _, pkgPath := range pkgs { + pkgLog := log.With().Str("package-path", pkgPath).Logger() + pkgLog.Trace().Msg("merging for package") + packageConfig, err := c.getPackageConfigMap(ctx, pkgPath) + if err != nil { + pkgLog.Err(err).Msg("failed to get package config") + return fmt.Errorf("failed to get package config: %w", err) + } + configSectionUntyped, configExists := packageConfig["config"] + if !configExists { + packageConfig["config"] = defaultCfg + continue + } + packageConfigSection := configSectionUntyped.(map[string]any) + + for key, value := range defaultCfg { + if contains([]string{"packages", "config"}, key) { + continue + } + _, keyExists := packageConfigSection[key] + if !keyExists { + packageConfigSection[key] = value + } + } + interfaces, err := c.getInterfacesForPackage(ctx, pkgPath) + if err != nil { + return fmt.Errorf("failed to get interfaces for package: %w", err) + } + for _, interfaceName := range interfaces { + interfacesSection, err := c.getInterfacesSection(ctx, pkgPath) + if err != nil { + return err + } + interfaceSectionUntyped, exists := interfacesSection[interfaceName] + if !exists { + continue + } + interfaceSection, ok := interfaceSectionUntyped.(map[string]any) + if !ok { + // assume interfaceSection value is nil + continue + } + + interfaceConfigSectionUntyped, exists := interfaceSection["config"] + if !exists { + interfaceSection["config"] = map[string]any{} + } + + interfaceConfigSection, ok := interfaceConfigSectionUntyped.(map[string]any) + if !ok { + // Assume this interface's value in the map is nil. Just skip it. + continue + } + for key, value := range packageConfigSection { + if key == "packages" { + continue + } + if _, keyExists := interfaceConfigSection[key]; !keyExists { + interfaceConfigSection[key] = value + } + } + } + } + + return nil + +} + +func (c *Config) getInterfacesForPackage(ctx context.Context, pkgPath string) ([]string, error) { + interfaces := []string{} + packageMap, err := c.getPackageConfigMap(ctx, pkgPath) + if err != nil { + return nil, err + } + interfacesUntyped, exists := packageMap["interfaces"] + if !exists { + return interfaces, nil + } + + interfacesMap := interfacesUntyped.(map[string]any) + for key := range interfacesMap { + interfaces = append(interfaces, key) + } + return interfaces, nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e717a87c..dfd2bb6b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -781,3 +781,194 @@ packages: }) } } + +func TestConfig_Initialize(t *testing.T) { + tests := []struct { + name string + cfgYaml string + wantCfgMap string + wantErr bool + }{ + { + name: "test with no subpackages present", + cfgYaml: ` +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/foo: + config: + recursive: True + all: True`, + wantCfgMap: `packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/foo: + config: + all: true + recursive: true +`, + }, + { + name: "test with one subpackage present", + cfgYaml: ` +with-expecter: False +dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + recursive: True + with-expecter: True + all: True`, + wantCfgMap: `dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + all: true + dir: foobar + recursive: true + with-expecter: true + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + config: + all: true + dir: foobar + recursive: true + with-expecter: true +with-expecter: false +`, + }, + { + name: "test with one subpackage, config already defined", + cfgYaml: ` +with-expecter: False +dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + recursive: True + with-expecter: True + all: True + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + config: + recursive: True + with-expecter: True + all: false + note: note + dir: barbaz`, + wantCfgMap: `dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + all: true + dir: foobar + recursive: true + with-expecter: true + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + config: + all: false + dir: barbaz + note: note + recursive: true + with-expecter: true +with-expecter: false +`, + }, + { + name: "test with one subpackage, config not defined", + cfgYaml: ` +with-expecter: False +dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + recursive: True + with-expecter: True + all: True + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: {} +`, + wantCfgMap: `dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + all: true + dir: foobar + recursive: true + with-expecter: true + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + config: + all: true + dir: foobar + recursive: true + with-expecter: true +with-expecter: false +`, + }, + { + name: "test with subpackage's interfaces defined", + cfgYaml: ` +with-expecter: False +dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + recursive: True + with-expecter: True + all: True + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + interfaces: + Getter: + config: + with-expecter: False`, + wantCfgMap: `dir: foobar +packages: + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2: + config: + all: true + dir: foobar + recursive: true + with-expecter: true + github.com/vektra/mockery/v2/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3: + config: + all: true + dir: foobar + recursive: true + with-expecter: true + interfaces: + Getter: + config: + all: true + dir: foobar + recursive: true + with-expecter: false +with-expecter: false +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpdir := pathlib.NewPath(t.TempDir()) + cfg := tmpdir.Join("config.yaml") + require.NoError(t, cfg.WriteFile([]byte(tt.cfgYaml))) + + c := &Config{ + Config: cfg.String(), + } + log, err := logging.GetLogger("TRACE") + require.NoError(t, err) + + if err := c.Initialize(log.WithContext(context.Background())); (err != nil) != tt.wantErr { + t.Errorf("Config.Initialize() error = %v, wantErr %v", err, tt.wantErr) + } + + cfgAsStr, err := yaml.Marshal(c._cfgAsMap) + require.NoError(t, err) + + if !reflect.DeepEqual(string(cfgAsStr), tt.wantCfgMap) { + t.Errorf(`Config.Initialize resultant config map +got +---- +%v + +want +------ +%v`, string(cfgAsStr), tt.wantCfgMap) + } + + }) + } +} diff --git a/pkg/fixtures/example_project/pkg_with_subpkgs/foo.go b/pkg/fixtures/example_project/pkg_with_subpkgs/foo.go new file mode 100644 index 00000000..480f9084 --- /dev/null +++ b/pkg/fixtures/example_project/pkg_with_subpkgs/foo.go @@ -0,0 +1 @@ +package pkg_with_subpkgs diff --git a/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg1/foo.go b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg1/foo.go new file mode 100644 index 00000000..e9b0ffd8 --- /dev/null +++ b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg1/foo.go @@ -0,0 +1 @@ +package subpkg1 diff --git a/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/foo.go b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/foo.go new file mode 100644 index 00000000..609d3fa1 --- /dev/null +++ b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/foo.go @@ -0,0 +1 @@ +package subpkg2 diff --git a/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3/foo.go b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3/foo.go new file mode 100644 index 00000000..fbcefc8d --- /dev/null +++ b/pkg/fixtures/example_project/pkg_with_subpkgs/subpkg2/subpkg3/foo.go @@ -0,0 +1,5 @@ +package subpkg3 + +type Getter interface { + Get(string) string +} diff --git a/pkg/generator.go b/pkg/generator.go index 7eef1a03..59295f3b 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -185,16 +185,24 @@ func (g *Generator) checkReplaceType(ctx context.Context, f func(from *replaceTy } func (g *Generator) addPackageImportWithName(ctx context.Context, path, name string) string { + log := zerolog.Ctx(ctx) + replaced := false g.checkReplaceType(ctx, func(from *replaceType, to *replaceType) bool { if path == from.pkg { + log.Debug().Str("from", path).Str("to", to.pkg).Msg("changing package path") + replaced = true path = to.pkg if to.alias != "" { + log.Debug().Str("from", name).Str("to", to.alias).Msg("changing alias name") name = to.alias } return false } return true }) + if replaced { + log.Debug().Str("to-path", path).Str("to-name", name).Msg("successfully replaced type") + } if existingName, pathExists := g.packagePathToName[path]; pathExists { return existingName