diff --git a/.boilerplate.txt b/.boilerplate.txt new file mode 100644 index 00000000..7832f90f --- /dev/null +++ b/.boilerplate.txt @@ -0,0 +1 @@ +// TEST MOCKERY BOILERPLATE \ No newline at end of file diff --git a/.mockery.yaml b/.mockery.yaml index 46232610..fe0803b1 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -1,15 +1,17 @@ disable-version-string: True mockname: "Mock{{.InterfaceName}}" filename: "mockery_mock.go" -outpkg: mocks +pkgname: mocks tags: "custom2" issue-845-fix: True template: "mockery" +boilerplate-file: "./.boilerplate.txt" +mock-build-tags: test _anchors: &inpackage_config all: True dir: "{{.InterfaceDir}}" mockname: "Mock{{.InterfaceName}}" - outpkg: "{{.PackageName}}_test" + pkgname: "{{.PackageName}}_test" filename: "mock_{{.InterfaceNameSnake}}_test.go" packages: #github.com/vektra/mockery/v2/pkg/fixtures/buildtag/comment: @@ -26,6 +28,7 @@ packages: all: False interfaces: A: + RequesterGenerics: #RequesterArgSameAsNamedImport: #RequesterVariadic: # configs: @@ -52,20 +55,20 @@ packages: # dir: "{{.InterfaceDir}}" # filename: "{{.InterfaceName}}_mock.go" # mockname: "Mock{{.InterfaceName}}" - # outpkg: "{{.PackageName}}" + # pkgname: "{{.PackageName}}" #github.com/vektra/mockery/v2/pkg/fixtures/empty_return: # config: # all: True # dir: "{{.InterfaceDir}}" # mockname: "{{.InterfaceName}}Mock" - # outpkg: "{{.PackageName}}" + # pkgname: "{{.PackageName}}" # filename: "mock_{{.InterfaceName}}_test.go" #github.com/vektra/mockery/v2/pkg/fixtures/method_args/same_name_arg_and_type: # config: # all: True # dir: "{{.InterfaceDir}}" # mockname: "{{.InterfaceName}}Mock" - # outpkg: "{{.PackageName}}" + # pkgname: "{{.PackageName}}" # filename: "mock_{{.InterfaceName}}_test.go" #github.com/vektra/mockery/v2/pkg/fixtures/iface_typed_param: # config: *inpackage_config @@ -91,5 +94,5 @@ packages: # all: True # dir: "{{.InterfaceDir}}" # filename: "mock_{{.MockName}}_test.go" - # outpkg: "{{.PackageName}}_test" + # pkgname: "{{.PackageName}}_test" # \ No newline at end of file diff --git a/cmd/mockery.go b/cmd/mockery.go index 922f4d97..873759dd 100644 --- a/cmd/mockery.go +++ b/cmd/mockery.go @@ -45,8 +45,6 @@ func NewRootCmd() (*cobra.Command, error) { pFlags := cmd.PersistentFlags() pFlags.StringVar(&cfgFile, "config", "", "config file to use") - pFlags.String("outpkg", "mocks", "name of generated package") - pFlags.String("packageprefix", "", "prefix for the generated package name, it is ignored if outpkg is also specified.") pFlags.String("dir", "", "directory to search for interfaces") pFlags.BoolP("recursive", "r", false, "recurse search into sub-directories") pFlags.StringArray("exclude", nil, "prefixes of subdirectories and files to exclude from search") @@ -334,9 +332,8 @@ func (r *RootApp) Run() error { generator, err := pkg.NewTemplateGenerator( interfacesInFile.interfaces[0].Pkg, outPkgPath, - interfacesInFile.interfaces[0].Config.Template, + packageConfig.Template, pkg.Formatter(r.Config.Formatter), - interfacesInFile.interfaces[0].Config.Outpkg, packageConfig, ) if err != nil { diff --git a/mocks/github.com/vektra/mockery/v2/pkg/fixtures/mockery_mock.go b/mocks/github.com/vektra/mockery/v2/pkg/fixtures/mockery_mock.go index 9d0a6c00..3c87a7d9 100644 --- a/mocks/github.com/vektra/mockery/v2/pkg/fixtures/mockery_mock.go +++ b/mocks/github.com/vektra/mockery/v2/pkg/fixtures/mockery_mock.go @@ -1,13 +1,55 @@ +// TEST MOCKERY BOILERPLATE // Code generated by mockery; DO NOT EDIT. // github.com/vektra/mockery +//go:build test + package mocks import ( + "io" + mock "github.com/stretchr/testify/mock" - "github.com/vektra/mockery/v2/pkg/fixtures" + test "github.com/vektra/mockery/v2/pkg/fixtures" + "github.com/vektra/mockery/v2/pkg/fixtures/constraints" ) +// NewMockRequesterGenerics creates a new instance of MockRequesterGenerics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRequesterGenerics[TAny any, TComparable comparable, TSigned constraints.Signed, TIntf test.GetInt, TExternalIntf io.Writer, TGenIntf test.GetGeneric[TSigned], TInlineType interface{ ~int | ~uint }, TInlineTypeGeneric interface { + ~int | test.GenericType[int, test.GetInt] + comparable +}](t interface { + mock.TestingT + Cleanup(func()) +}) *MockRequesterGenerics[TAny, TComparable, TSigned, TIntf, TExternalIntf, TGenIntf, TInlineType, TInlineTypeGeneric] { + mock := &MockRequesterGenerics[TAny, TComparable, TSigned, TIntf, TExternalIntf, TGenIntf, TInlineType, TInlineTypeGeneric]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockRequesterGenerics is an autogenerated mock type for the RequesterGenerics type +type MockRequesterGenerics[TAny any, TComparable comparable, TSigned constraints.Signed, TIntf test.GetInt, TExternalIntf io.Writer, TGenIntf test.GetGeneric[TSigned], TInlineType interface{ ~int | ~uint }, TInlineTypeGeneric interface { + ~int | test.GenericType[int, test.GetInt] + comparable +}] struct { + mock.Mock +} + +type MockRequesterGenerics_expecter[TAny any, TComparable comparable, TSigned constraints.Signed, TIntf test.GetInt, TExternalIntf io.Writer, TGenIntf test.GetGeneric[TSigned], TInlineType interface{ ~int | ~uint }, TInlineTypeGeneric interface { + ~int | test.GenericType[int, test.GetInt] + comparable +}] struct { + mock *mock.Mock +} + +func (_m *MockRequesterGenerics[TAny, TComparable, TSigned, TIntf, TExternalIntf, TGenIntf, TInlineType, TInlineTypeGeneric]) EXPECT() *MockRequesterGenerics_expecter[TAny, TComparable, TSigned, TIntf, TExternalIntf, TGenIntf, TInlineType, TInlineTypeGeneric] { + return &MockRequesterGenerics_expecter[TAny, TComparable, TSigned, TIntf, TExternalIntf, TGenIntf, TInlineType, TInlineTypeGeneric]{mock: &_m.Mock} +} + // NewMockA creates a new instance of MockA. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockA(t interface { @@ -21,3 +63,16 @@ func NewMockA(t interface { return mock } + +// MockA is an autogenerated mock type for the A type +type MockA struct { + mock.Mock +} + +type MockA_expecter struct { + mock *mock.Mock +} + +func (_m *MockA) EXPECT() *MockA_expecter { + return &MockA_expecter{mock: &_m.Mock} +} diff --git a/pkg/config.go b/pkg/config.go index ed53a830..3f7a5488 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -57,7 +57,6 @@ type Config struct { MockName string `mapstructure:"mockname"` Note string `mapstructure:"note"` PkgName string `mapstructure:"pkgname"` - Packageprefix string `mapstructure:"packageprefix"` Packages map[string]interface{} `mapstructure:"packages"` Profile string `mapstructure:"profile"` Recursive bool `mapstructure:"recursive"` @@ -84,8 +83,9 @@ func NewConfigFromViper(v *viper.Viper) (*Config, error) { v.SetDefault("dir", "mocks/{{.PackagePath}}") v.SetDefault("filename", "mock_{{.InterfaceName}}.go") + v.SetDefault("formatter", "goimports") v.SetDefault("mockname", "Mock{{.InterfaceName}}") - v.SetDefault("outpkg", "{{.PackageName}}") + v.SetDefault("pkgname", "{{.PackageName}}") v.SetDefault("dry-run", false) v.SetDefault("log-level", "info") diff --git a/pkg/registry/method_scope.go b/pkg/registry/method_scope.go index dea6a5e8..5b7e99ff 100644 --- a/pkg/registry/method_scope.go +++ b/pkg/registry/method_scope.go @@ -2,6 +2,7 @@ package registry import ( "context" + "fmt" "go/types" "strconv" @@ -136,15 +137,29 @@ func (m MethodScope) populateImports(ctx context.Context, t types.Type, imports m.populateImports(ctx, t.Field(i).Type(), imports) } + case *types.Union: + log.Debug().Int("len", t.Len()).Msg("found union") + for i := 0; i < t.Len(); i++ { + term := t.Term(i) + m.populateImports(ctx, term.Type(), imports) + } case *types.Interface: // anonymous interface + log.Debug(). + Int("num-methods", t.NumMethods()). + Int("num-explicit-methods", t.NumExplicitMethods()). + Int("num-embeddeds", t.NumEmbeddeds()). + Msg("found interface") for i := 0; i < t.NumExplicitMethods(); i++ { + log.Debug().Msg("populating import from explicit method") m.populateImports(ctx, t.ExplicitMethod(i).Type(), imports) } for i := 0; i < t.NumEmbeddeds(); i++ { + log.Debug().Msg("populating import form embedded type") m.populateImports(ctx, t.EmbeddedType(i), imports) } + default: - log.Debug().Msg("unable to determine type of object") + log.Debug().Str("real-type", fmt.Sprintf("%T", t)).Msg("unable to determine type of object") } } diff --git a/pkg/registry/var.go b/pkg/registry/var.go index de8da2dc..4cc3163a 100644 --- a/pkg/registry/var.go +++ b/pkg/registry/var.go @@ -34,7 +34,6 @@ func (v Var) packageQualifier(pkg *types.Package) string { if v.moqPkgPath != "" && v.moqPkgPath == path { return "" } - return v.imports[path].Qualifier() } diff --git a/pkg/template/mockery.templ b/pkg/template/mockery.templ index 01d8f504..72120333 100644 --- a/pkg/template/mockery.templ +++ b/pkg/template/mockery.templ @@ -1,6 +1,12 @@ +{{ .Boilerplate }} // Code generated by mockery; DO NOT EDIT. // github.com/vektra/mockery +{{- if .BuildTags }} + +//go:build {{ .BuildTags }} +{{- end }} + package {{.PkgName}} import ( @@ -13,35 +19,29 @@ import ( {{- range $i, $mock := .Mocks }} // New{{ .MockName }} creates a new instance of {{ .MockName }}. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func New{{ .MockName}} -{{- if .TypeParams -}} - [ - {{- range $index, $param := .TypeParams}} - {{- if $index}}, {{end}}{{$param.Name | Exported}} {{$param.TypeString}} - {{- end -}} - ] -{{- end }} (t interface { +func New{{ .MockName }}{{ $mock | TypeConstraint }} (t interface { mock.TestingT Cleanup(func()) -}) *{{ .MockName }} -{{- if .TypeParams -}} - [ - {{- range $index, $param := .TypeParams}} - {{- if $index}}, {{end}}{{$param.Name | Exported}} - {{- end -}} - ] -{{- end }} { - mock := &{{ .MockName }}{{- if .TypeParams -}} - [ - {{- range $index, $param := .TypeParams}} - {{- if $index}}, {{end}}{{$param.Name | Exported}} - {{- end -}} - ] -{{- end }}{} +}) *{{ .MockName }}{{ $mock | TypeInstantiation }} { + mock := &{{ .MockName }}{{ $mock | TypeInstantiation }}{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) return mock } + +// {{ .MockName }} is an autogenerated mock type for the {{ .InterfaceName }} type +type {{ .MockName }}{{ $mock | TypeConstraint }} struct { + mock.Mock +} + +type {{.MockName}}_expecter{{ $mock | TypeConstraint }} struct { + mock *mock.Mock +} + +func (_m *{{.MockName}}{{ $mock | TypeInstantiation }}) EXPECT() *{{.MockName}}_expecter{{ $mock | TypeInstantiation }} { + return &{{.MockName}}_expecter{{ $mock | TypeInstantiation }}{mock: &_m.Mock} +} {{- end }} + diff --git a/pkg/template/template.go b/pkg/template/template.go index 1ede13e0..92da5a49 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -36,7 +36,7 @@ func New(style string) (Template, error) { return Template{}, stackerr.NewStackErrf(nil, "style %s does not exist", style) } - tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(templateString) + tmpl, err := template.New(style).Funcs(templateFuncs).Parse(templateString) if err != nil { return Template{}, err } @@ -87,7 +87,17 @@ var templateFuncs = template.FuncMap{ return "sync" }, "Exported": exported, - "TypeInstantiation": func(m MockData) string { + + "MocksSomeMethod": func(mocks []MockData) bool { + for _, m := range mocks { + if len(m.Methods) > 0 { + return true + } + } + + return false + }, + "TypeConstraintTest": func(m MockData) string { if len(m.TypeParams) == 0 { return "" } @@ -97,17 +107,40 @@ var templateFuncs = template.FuncMap{ s += ", " } s += exported(param.Name()) + s += " " + s += param.TypeString() } s += "]" return s }, - "MocksSomeMethod": func(mocks []MockData) bool { - for _, m := range mocks { - if len(m.Methods) > 0 { - return true + "TypeConstraint": func(m MockData) string { + if len(m.TypeParams) == 0 { + return "" + } + s := "[" + for idx, param := range m.TypeParams { + if idx != 0 { + s += ", " } + s += exported(param.Name()) + s += " " + s += param.TypeString() } - - return false + s += "]" + return s + }, + "TypeInstantiation": func(m MockData) string { + if len(m.TypeParams) == 0 { + return "" + } + s := "[" + for idx, param := range m.TypeParams { + if idx != 0 { + s += ", " + } + s += exported(param.Name()) + } + s += "]" + return s }, } diff --git a/pkg/template/template_data.go b/pkg/template/template_data.go index 414ea79e..04eaee50 100644 --- a/pkg/template/template_data.go +++ b/pkg/template/template_data.go @@ -10,6 +10,8 @@ import ( // Data is the template data used to render the Moq template. type Data struct { + Boilerplate string + BuildTags string PkgName string SrcPkgQualifier string Imports []*registry.Package diff --git a/pkg/template_generator.go b/pkg/template_generator.go index 884b3a0f..18ce301a 100644 --- a/pkg/template_generator.go +++ b/pkg/template_generator.go @@ -8,8 +8,10 @@ import ( "go/token" "go/types" + "github.com/chigopher/pathlib" "github.com/rs/zerolog" "github.com/vektra/mockery/v2/pkg/registry" + "github.com/vektra/mockery/v2/pkg/stackerr" "github.com/vektra/mockery/v2/pkg/template" "golang.org/x/tools/go/packages" "golang.org/x/tools/imports" @@ -18,8 +20,9 @@ import ( type Formatter string const ( + FORMAT_GOFMT Formatter = "gofmt" FORMAT_GOIMPORRTS Formatter = "goimports" - FORMAT_NOOP Formatter = "noop" + FORMAT_NOOP Formatter = "" ) type TemplateGenerator struct { @@ -55,12 +58,13 @@ func (g *TemplateGenerator) format(src []byte) ([]byte, error) { switch g.formatter { case FORMAT_GOIMPORRTS: return goimports(src) - - case FORMAT_NOOP: + case FORMAT_GOFMT: + return gofmt(src) + case "": return src, nil } - return gofmt(src) + return nil, fmt.Errorf("unknown formatter type: %s", g.formatter) } func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func) template.MethodData { @@ -153,8 +157,21 @@ func (g *TemplateGenerator) Generate( TemplateData: ifaceMock.Config.TemplateData, }) } + var boilerplate string + if g.pkgConfig.BoilerplateFile != "" { + var err error + boilerplatePath := pathlib.NewPath(g.pkgConfig.BoilerplateFile) + boilerplateBytes, err := boilerplatePath.ReadFile() + if err != nil { + log.Err(err).Msg("unable to find boilerplate file") + return nil, stackerr.NewStackErr(err) + } + boilerplate = string(boilerplateBytes) + } data := template.Data{ + Boilerplate: boilerplate, + BuildTags: g.pkgConfig.MockBuildTags, PkgName: g.pkgConfig.PkgName, SrcPkgQualifier: "", Mocks: mockData, @@ -181,8 +198,8 @@ func (g *TemplateGenerator) Generate( // grab the formatter as specified in the topmost interface-level config. formatted, err := g.format(buf.Bytes()) if err != nil { - log.Err(err).Msg("can't format mock file, printing buffer.") fmt.Print(buf.String()) + log.Err(err).Msg("can't format mock file") return []byte{}, fmt.Errorf("formatting mock file: %w", err) } return formatted, nil