diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index bcb00877c..e1f90b6b9 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,7 +1,7 @@ name: documentation on: push: - branches: [ v3 ] + branches: [ master, v3 ] permissions: contents: write jobs: @@ -28,4 +28,4 @@ jobs: - name: Deploy docs run: "mike deploy --push --update-aliases $(grep VERSION mockery-tools.env | cut -d'=' -f 2 | cut -d'.' -f1-2) v3" env: - GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }} + GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }} \ No newline at end of file diff --git a/.github/workflows/reusable-testing.yml b/.github/workflows/reusable-testing.yml index 88006bd68..a335ff30a 100644 --- a/.github/workflows/reusable-testing.yml +++ b/.github/workflows/reusable-testing.yml @@ -27,7 +27,7 @@ jobs: go-version: ${{ matrix.go_vers }} - name: Download dependencies - run: go mod download + run: go mod download -x - name: Test run: go run github.com/go-task/task/v3/cmd/task test.ci \ No newline at end of file diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml index 24b0afc5e..79219c049 100644 --- a/.github/workflows/tag-and-release.yml +++ b/.github/workflows/tag-and-release.yml @@ -6,12 +6,11 @@ on: permissions: contents: write jobs: - # TODO: temporarily disable testing during alpha development - #test: - # uses: ./.github/workflows/reusable-testing.yml + test: + uses: ./.github/workflows/reusable-testing.yml tag: runs-on: ubuntu-latest - #needs: test + needs: test outputs: tag_result: ${{ steps.tag.outputs.tag_result }} requested_version: ${{ steps.tag.outputs.requested_version }} @@ -96,4 +95,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} HOMEBREW_TAP_TOKEN: ${{ secrets.GORELEASER_HOMEBREW_TAP_TOKEN }} GORELEASER_CURRENT_TAG: ${{ needs.tag.outputs.requested_version }} - #GORELEASER_PREVIOUS_TAG: ${{ needs.tag.outputs.previous_version }} + #GORELEASER_PREVIOUS_TAG: ${{ needs.tag.outputs.previous_version }} \ No newline at end of file diff --git a/.github/workflows/testing-dispatch.yml b/.github/workflows/testing-dispatch.yml index a1491c4bb..a5b4b7520 100644 --- a/.github/workflows/testing-dispatch.yml +++ b/.github/workflows/testing-dispatch.yml @@ -12,4 +12,4 @@ jobs: test: uses: ./.github/workflows/reusable-testing.yml with: - ref: ${{ inputs.ref }} + ref: ${{ inputs.ref }} \ No newline at end of file diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0a6df37f8..2aad00272 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -2,14 +2,8 @@ name: Go Test on: pull_request: - branches: [v3] + branches: [ master, v3 ] jobs: test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: ./.github/workflows/reusable-testing.yml - + uses: ./.github/workflows/reusable-testing.yml \ No newline at end of file diff --git a/.mockery.yml b/.mockery.yml index a53b6d667..b64f8579b 100644 --- a/.mockery.yml +++ b/.mockery.yml @@ -10,6 +10,20 @@ template-data: boilerplate-file: "./.boilerplate.txt" packages: github.com/vektra/mockery/v3/internal/fixtures/buildtag/comment: + github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type: + config: + recursive: True + interfaces: + RType: + configs: + - {} + - mockname: RTypeReplaced1 + replace-type: + github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1: + RType1: + pkg-path: github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2 + type-name: RType2 + github.com/vektra/mockery/v3/internal/fixtures: interfaces: RequesterVariadic: diff --git a/Taskfile.yml b/Taskfile.yml index 9e17585bf..065b303db 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -12,7 +12,7 @@ tasks: sources: - "**/*.go" cmds: - - go fmt ./... + - gofumpt -l -w . mocks: desc: generate new mocks from scratch @@ -83,11 +83,11 @@ tasks: - ./e2e/run_all.sh test.ci: - deps: [fmt, lint] + deps: [lint] cmds: - - task: mocks.remove - - task: mocks.generate + - task: mocks - task: test + - task: mocks.remove - task: test.e2e default: diff --git a/docs/configuration.md b/docs/configuration.md index ec7f8181c..51ede9fd0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -57,6 +57,7 @@ Parameter Descriptions | `exclude-subpkg-regex` | :fontawesome-solid-x: | `#!yaml []` | A list of regular expressions that denote which subpackages should be excluded when `#!yaml recursive: true` | | `exclude-regex` | :fontawesome-solid-x: | `#!yaml ""` | When set along with `include-regex`, then interfaces which match `include-regex` but also match `exclude-regex` will not be generated. If `all` is set, or if `include-regex` is not set, then `exclude-regex` has no effect. | | `filename` | :fontawesome-solid-check: | `#!yaml "mock_{{.InterfaceName}}.go"` | The name of the file the mock will reside in. | +| `force-file-write` | :fontawesome-solid-x: | `#!yaml false` | When set to `#!yaml force-file-write: true`, mockery will forcibly overwrite any existing files. | | `formatter` | :fontawesome-solid-x: | `#!yaml "goimports"` | The formatter to use on the rendered template. Choices are: `gofmt`, `goimports`, `noop`. | | `include-regex` | :fontawesome-solid-x: | `#!yaml ""` | When set, only interface names that match the expression will be generated. This setting is ignored if `all: True` is specified in the configuration. To further refine the interfaces generated, use `exclude-regex`. | | `log-level` | :fontawesome-solid-x: | `#!yaml "info"` | Set the level of the logger | @@ -65,11 +66,25 @@ Parameter Descriptions | [`packages`](features.md#packages-configuration) | :fontawesome-solid-x: | `#!yaml null` | A dictionary containing configuration describing the packages and interfaces to generate mocks for. | | `pkgname` | :fontawesome-solid-check: | `#!yaml "{{.SrcPackageName}}" | The `#!go package name` given to the generated mock files. | | [`recursive`](features.md#recursive-package-discovery) | :fontawesome-solid-x: | `#!yaml false` | When set to `true` on a particular package, mockery will recursively search for all sub-packages and inject those packages into the config map. | +| [`replace-type`](replace-type.md) | :fontawesome-solid-x: | `#!yaml {}` | Use this parameter to specify type replacements. | | `tags` | :fontawesome-solid-x: | `#!yaml ""` | A space-separated list of additional build tags to load packages. | | `template` | :fontawesome-solid-x: | `#!yaml ""` | The template to use. The choices are `moq`, `mockery`, or a file path provided by `file://path/to/file.txt`. | | `template-data` | :fontawesome-solid-x: | `#!yaml {}` | A `map[string]any` that provides arbitrary options to the template. Each template will have a different set of accepted keys. Refer to each template's documentation for more details. | +Config Templates +---------------- + +Parameters marked as being templated have access to a number of template variables and functions. + +### Variables + +The variables provided are specified in the [`ConfigData`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#ConfigData) struct. + +### Functions + +All of the functions defined in [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) are available to templated parameters. + Merging Precedence ------------------ diff --git a/docs/replace-type.md b/docs/replace-type.md new file mode 100644 index 000000000..53cf6df5a --- /dev/null +++ b/docs/replace-type.md @@ -0,0 +1,57 @@ +--- +title: replace-type +--- + +## Description + +The `#!yaml replace-type:` parameter allows you to replace a type in the generated mocks with another type. Take for example the following interface: + + +```go title="interface.go" +package replace_type + +import ( + "github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1" + "github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2" +) + +type RType interface { + Replace1(f rt1.RType1) +} +``` + +You can selectively replace the `rt1.RType1` with a new type if so desired. For example: + +```yaml title=".mockery.yml" +replace-type: + github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1: + RType1: + pkg-path: github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2 + type-name: RType2 +``` + +The mock will now replace all instances of `rt1.RType1` with `rt2.RType2`. You can see the before and after of `mockery`-style mocks: + +=== "before" + + ```go + // Replace2 provides a mock function for the type RTypeReplaced1 + func (_mock *RTypeReplaced1) Replace1(f rt1.RType1) { + _mock.Called(f) + return + } + ``` + +=== "after" + + ```go + // Replace2 provides a mock function for the type RTypeReplaced1 + func (_mock *RTypeReplaced1) Replace1(f rt2.RType2) { + _mock.Called(f) + return + } + ``` + +## Background + +This parameter is useful if you need to need to work around packages that use internal types. Take for example the situation found [here](https://github.com/vektra/mockery/issues/864#issuecomment-2567788637), noted by [RangelReale](https://github.com/RangelReale). diff --git a/e2e/test_infinite_mocking.sh b/e2e/test_infinite_mocking.sh index c43be2051..9cc30c11f 100755 --- a/e2e/test_infinite_mocking.sh +++ b/e2e/test_infinite_mocking.sh @@ -4,6 +4,7 @@ # New mocks may legimitately be created, so we run mockery once first num_files_before=$(find . -type f | wc -l) +export MOCKERY_FORCE_FILE_WRITE="true" go run github.com/go-task/task/v3/cmd/task mocks.generate num_files_after=$(find . -type f | wc -l) diff --git a/e2e/test_mockery_generation.sh b/e2e/test_mockery_generation.sh index 543974e4a..46df36dbd 100755 --- a/e2e/test_mockery_generation.sh +++ b/e2e/test_mockery_generation.sh @@ -1,6 +1,6 @@ #!/bin/bash -go run github.com/go-task/task/v3/cmd/task mocks.generate +go run github.com/go-task/task/v3/cmd/task mocks rt=$? if [ $rt -ne 0 ]; then echo "ERROR: non-zero return code from mockery" diff --git a/e2e/test_recursive_package_with_only_autogenerated_files.sh b/e2e/test_recursive_package_with_only_autogenerated_files.sh deleted file mode 100755 index 809ed4c38..000000000 --- a/e2e/test_recursive_package_with_only_autogenerated_files.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# This tests https://github.com/vektra/mockery/pull/682 - -FILE="pkg/fixtures/recursive_generation/subpkg_with_only_autogenerated_files/Foo_mock.go" -if ! [ -f $FILE ]; then - echo "ERROR: $FILE does not exist" - exit 1 -fi diff --git a/internal/cmd/mockery.go b/internal/cmd/mockery.go index 91fde6584..fcc6e25a7 100644 --- a/internal/cmd/mockery.go +++ b/internal/cmd/mockery.go @@ -14,12 +14,11 @@ import ( pkg "github.com/vektra/mockery/v3/internal" "github.com/vektra/mockery/v3/internal/logging" "github.com/vektra/mockery/v3/internal/stackerr" + "github.com/vektra/mockery/v3/template" "golang.org/x/tools/go/packages" ) -var ( - ErrCfgFileNotFound = errors.New("config file not found") -) +var ErrCfgFileNotFound = errors.New("config file not found") func NewRootCmd() (*cobra.Command, error) { var pFlags *pflag.FlagSet @@ -81,18 +80,18 @@ func Execute() { if err != nil { os.Exit(1) } - if cmd.Execute(); err != nil { + if err := cmd.Execute(); err != nil { os.Exit(1) } } type RootApp struct { - Config pkg.RootConfig + Config template.RootConfig } func GetRootApp(ctx context.Context, flags *pflag.FlagSet) (*RootApp, error) { r := &RootApp{} - config, _, err := pkg.NewRootConfig(ctx, flags) + config, _, err := template.NewRootConfig(ctx, flags) if err != nil { return nil, stackerr.NewStackErrf(err, "failed to get config") } @@ -113,7 +112,7 @@ type InterfaceCollection struct { outFilePath *pathlib.Path srcPkg *packages.Package outPkgName string - interfaces []*pkg.Interface + interfaces []*template.Interface template string } @@ -122,19 +121,19 @@ func NewInterfaceCollection( outFilePath *pathlib.Path, srcPkg *packages.Package, outPkgName string, - template string, + templ string, ) *InterfaceCollection { return &InterfaceCollection{ srcPkgPath: srcPkgPath, outFilePath: outFilePath, srcPkg: srcPkg, outPkgName: outPkgName, - interfaces: make([]*pkg.Interface, 0), - template: template, + interfaces: make([]*template.Interface, 0), + template: templ, } } -func (i *InterfaceCollection) Append(ctx context.Context, iface *pkg.Interface) error { +func (i *InterfaceCollection) Append(ctx context.Context, iface *template.Interface) error { collectionFilepath := i.outFilePath.String() interfaceFilepath := iface.Config.FilePath().String() log := zerolog.Ctx(ctx).With(). @@ -258,7 +257,7 @@ func (r *RootApp) Run() error { } if err := mockFileToInterfaces[filePath.String()].Append( ctx, - pkg.NewInterface( + template.NewInterface( iface.Name, iface.FileName, iface.File, @@ -313,8 +312,8 @@ func (r *RootApp) Run() error { fileLog.Err(err).Msg("can't determine if outfile exists") return fmt.Errorf("determining if outfile exists: %w", err) } - if outFileExists { - fileLog.Error().Msg("output file exists, can't write mocks") + if outFileExists && !packageConfig.Config.ForceFileWrite { + fileLog.Error().Bool("force-file-write", packageConfig.Config.ForceFileWrite).Msg("output file exists, can't write mocks") return fmt.Errorf("outfile exists") } diff --git a/internal/cmd/showconfig.go b/internal/cmd/showconfig.go index a09a46620..5b7b21bd3 100644 --- a/internal/cmd/showconfig.go +++ b/internal/cmd/showconfig.go @@ -8,8 +8,8 @@ import ( "github.com/knadh/koanf/providers/structs" "github.com/knadh/koanf/v2" "github.com/spf13/cobra" - pkg "github.com/vektra/mockery/v3/internal" "github.com/vektra/mockery/v3/internal/logging" + "github.com/vektra/mockery/v3/template" ) func NewShowConfigCmd() *cobra.Command { @@ -24,13 +24,16 @@ func NewShowConfigCmd() *cobra.Command { } ctx := log.WithContext(context.Background()) - conf, _, err := pkg.NewRootConfig(ctx, cmd.Parent().PersistentFlags()) + conf, _, err := template.NewRootConfig(ctx, cmd.Parent().PersistentFlags()) if err != nil { return err } k := koanf.New("|") - k.Load(structs.Provider(conf, "koanf"), nil) + if err := k.Load(structs.Provider(conf, "koanf"), nil); err != nil { + log.Err(err).Msg("failed to load config") + return err + } b, _ := k.Marshal(koanfYAML.Parser()) fmt.Println(string(b)) diff --git a/internal/errors.go b/internal/errors.go index dd360d42b..cd52bd29d 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -1,4 +1,4 @@ -package pkg +package internal import "fmt" diff --git a/internal/fixtures/example_project/replace_type/mocks_replace_type_test.go b/internal/fixtures/example_project/replace_type/mocks_replace_type_test.go new file mode 100644 index 000000000..1689bbb94 --- /dev/null +++ b/internal/fixtures/example_project/replace_type/mocks_replace_type_test.go @@ -0,0 +1,201 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// TEST MOCKERY BOILERPLATE + +package replace_type + +import ( + mock "github.com/stretchr/testify/mock" + "github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1" + "github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2" +) + +// NewMockRType creates a new instance of MockRType. 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 NewMockRType(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRType { + mock := &MockRType{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockRType is an autogenerated mock type for the RType type +type MockRType struct { + mock.Mock +} + +type MockRType_Expecter struct { + mock *mock.Mock +} + +func (_m *MockRType) EXPECT() *MockRType_Expecter { + return &MockRType_Expecter{mock: &_m.Mock} +} + +// Replace1 provides a mock function for the type MockRType +func (_mock *MockRType) Replace1(f rt1.RType1) { + _mock.Called(f) + return +} + +// MockRType_Replace1_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace1' +type MockRType_Replace1_Call struct { + *mock.Call +} + +// Replace1 is a helper method to define mock.On call +// - f +func (_e *MockRType_Expecter) Replace1(f interface{}) *MockRType_Replace1_Call { + return &MockRType_Replace1_Call{Call: _e.mock.On("Replace1", f)} +} + +func (_c *MockRType_Replace1_Call) Run(run func(f rt1.RType1)) *MockRType_Replace1_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(rt1.RType1)) + }) + return _c +} + +func (_c *MockRType_Replace1_Call) Return() *MockRType_Replace1_Call { + _c.Call.Return() + return _c +} + +func (_c *MockRType_Replace1_Call) RunAndReturn(run func(f rt1.RType1)) *MockRType_Replace1_Call { + _c.Run(run) + return _c +} + +// Replace2 provides a mock function for the type MockRType +func (_mock *MockRType) Replace2(f rt2.RType2) { + _mock.Called(f) + return +} + +// MockRType_Replace2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace2' +type MockRType_Replace2_Call struct { + *mock.Call +} + +// Replace2 is a helper method to define mock.On call +// - f +func (_e *MockRType_Expecter) Replace2(f interface{}) *MockRType_Replace2_Call { + return &MockRType_Replace2_Call{Call: _e.mock.On("Replace2", f)} +} + +func (_c *MockRType_Replace2_Call) Run(run func(f rt2.RType2)) *MockRType_Replace2_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(rt2.RType2)) + }) + return _c +} + +func (_c *MockRType_Replace2_Call) Return() *MockRType_Replace2_Call { + _c.Call.Return() + return _c +} + +func (_c *MockRType_Replace2_Call) RunAndReturn(run func(f rt2.RType2)) *MockRType_Replace2_Call { + _c.Run(run) + return _c +} + +// NewRTypeReplaced1 creates a new instance of RTypeReplaced1. 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 NewRTypeReplaced1(t interface { + mock.TestingT + Cleanup(func()) +}) *RTypeReplaced1 { + mock := &RTypeReplaced1{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// RTypeReplaced1 is an autogenerated mock type for the RType type +type RTypeReplaced1 struct { + mock.Mock +} + +type RTypeReplaced1_Expecter struct { + mock *mock.Mock +} + +func (_m *RTypeReplaced1) EXPECT() *RTypeReplaced1_Expecter { + return &RTypeReplaced1_Expecter{mock: &_m.Mock} +} + +// Replace1 provides a mock function for the type RTypeReplaced1 +func (_mock *RTypeReplaced1) Replace1(f rt2.RType2) { + _mock.Called(f) + return +} + +// RTypeReplaced1_Replace1_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace1' +type RTypeReplaced1_Replace1_Call struct { + *mock.Call +} + +// Replace1 is a helper method to define mock.On call +// - f +func (_e *RTypeReplaced1_Expecter) Replace1(f interface{}) *RTypeReplaced1_Replace1_Call { + return &RTypeReplaced1_Replace1_Call{Call: _e.mock.On("Replace1", f)} +} + +func (_c *RTypeReplaced1_Replace1_Call) Run(run func(f rt2.RType2)) *RTypeReplaced1_Replace1_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(rt2.RType2)) + }) + return _c +} + +func (_c *RTypeReplaced1_Replace1_Call) Return() *RTypeReplaced1_Replace1_Call { + _c.Call.Return() + return _c +} + +func (_c *RTypeReplaced1_Replace1_Call) RunAndReturn(run func(f rt2.RType2)) *RTypeReplaced1_Replace1_Call { + _c.Run(run) + return _c +} + +// Replace2 provides a mock function for the type RTypeReplaced1 +func (_mock *RTypeReplaced1) Replace2(f rt2.RType2) { + _mock.Called(f) + return +} + +// RTypeReplaced1_Replace2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Replace2' +type RTypeReplaced1_Replace2_Call struct { + *mock.Call +} + +// Replace2 is a helper method to define mock.On call +// - f +func (_e *RTypeReplaced1_Expecter) Replace2(f interface{}) *RTypeReplaced1_Replace2_Call { + return &RTypeReplaced1_Replace2_Call{Call: _e.mock.On("Replace2", f)} +} + +func (_c *RTypeReplaced1_Replace2_Call) Run(run func(f rt2.RType2)) *RTypeReplaced1_Replace2_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(rt2.RType2)) + }) + return _c +} + +func (_c *RTypeReplaced1_Replace2_Call) Return() *RTypeReplaced1_Replace2_Call { + _c.Call.Return() + return _c +} + +func (_c *RTypeReplaced1_Replace2_Call) RunAndReturn(run func(f rt2.RType2)) *RTypeReplaced1_Replace2_Call { + _c.Run(run) + return _c +} diff --git a/internal/fixtures/example_project/replace_type/rt_test.go b/internal/fixtures/example_project/replace_type/rt_test.go new file mode 100644 index 000000000..953ab4a01 --- /dev/null +++ b/internal/fixtures/example_project/replace_type/rt_test.go @@ -0,0 +1,21 @@ +package replace_type + +import ( + "strings" + "testing" + + "github.com/chigopher/pathlib" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReplaceType(t *testing.T) { + mockFile := pathlib.NewPath("./mocks_replace_type_test.go") + b, err := mockFile.ReadFile() + require.NoError(t, err) + // .mockery.yml replaced github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt1 + // with github.com/vektra/mockery/v3/internal/fixtures/example_project/replace_type/rti/rt2 + assert.True(t, strings.Contains(string(b), "*RTypeReplaced1) Replace1(f rt2.RType2) {")) + // This should contain no replaced type. + assert.True(t, strings.Contains(string(b), "*MockRType) Replace1(f rt1.RType1) {")) +} diff --git a/internal/fixtures/expecter_test.go b/internal/fixtures/expecter_test.go index 301334585..adb9dbf28 100644 --- a/internal/fixtures/expecter_test.go +++ b/internal/fixtures/expecter_test.go @@ -18,7 +18,7 @@ var ( // Test that the generated code for ExpecterTest interface is usable func TestExpecter(t *testing.T) { - expMock := NewMockExpecter(t) + expMock := MockExpecter{} t.Run("NoArg", func(t *testing.T) { var runCalled bool @@ -34,7 +34,7 @@ func TestExpecter(t *testing.T) { // Call again panic assert.Panics(t, func() { expMock.NoArg() - }, "call did not panic") + }) expMock.AssertExpectations(t) }) diff --git a/internal/fixtures/method_args/same_name_arg_and_type/entity_test.go b/internal/fixtures/method_args/same_name_arg_and_type/entity_test.go index 1a83c4456..5ad0ff154 100644 --- a/internal/fixtures/method_args/same_name_arg_and_type/entity_test.go +++ b/internal/fixtures/method_args/same_name_arg_and_type/entity_test.go @@ -37,7 +37,6 @@ func Test(t *testing.T) { } res := s.ExecDoB() assert.Equal(t, mockInterfaceB, res) - }) t.Run("ExecDoB0", func(t *testing.T) { mockInterfaceB0 := NewMockinterfaceB0(t) @@ -49,7 +48,6 @@ func Test(t *testing.T) { } res := s.ExecDoB0() assert.Equal(t, mockInterfaceB0, res) - }) t.Run("ExecDoB0v2", func(t *testing.T) { mockInterfaceB0 := NewMockinterfaceB0(t) @@ -61,6 +59,5 @@ func Test(t *testing.T) { } res := s.ExecDoB0v2() assert.Equal(t, mockInterfaceB0, res) - }) } diff --git a/internal/fixtures/requester_test.go b/internal/fixtures/requester_test.go index 4cfdbbff6..7eb336742 100644 --- a/internal/fixtures/requester_test.go +++ b/internal/fixtures/requester_test.go @@ -13,7 +13,7 @@ func TestRequesterMock(t *testing.T) { m.EXPECT().Get("foo").Return("bar", nil).Once() retString, err := m.Get("foo") assert.NoError(t, err) - assert.Equal(t, retString, "bar") + assert.Equal(t, "bar", retString) } func TestRequesterMockRunAndReturn(t *testing.T) { @@ -23,7 +23,7 @@ func TestRequesterMockRunAndReturn(t *testing.T) { }) retString, err := m.Get("hello") assert.NoError(t, err) - assert.Equal(t, retString, "hello world") + assert.Equal(t, "hello world", retString) } func TestRequesterMockRun(t *testing.T) { @@ -34,15 +34,16 @@ func TestRequesterMockRun(t *testing.T) { }) retString, err := m.Get("hello") assert.NoError(t, err) - assert.Equal(t, retString, "") + assert.Equal(t, "", retString) } +//nolint:errcheck func TestRequesterMockTestifyEmbed(t *testing.T) { m := NewMockRequester(t) m.EXPECT().Get(mock.Anything).Return("", nil).Twice() m.Get("hello") m.Get("world") - assert.Equal(t, len(m.Mock.Calls), 2) + assert.Len(t, m.Mock.Calls, 2) } func TestRequesterMoq(t *testing.T) { diff --git a/internal/fixtures/type_alias/interface_test.go b/internal/fixtures/type_alias/interface_test.go index 626ee3130..a00b68deb 100644 --- a/internal/fixtures/type_alias/interface_test.go +++ b/internal/fixtures/type_alias/interface_test.go @@ -17,7 +17,7 @@ func TestTypeAlias(t *testing.T) { }{ { name: "With alias unresolved", - filepath: "./mocks_test.go", + filepath: "./mocks_type_alias_test.go", expectedRegex: `func \(_mock \*MockInterface1\) Foo\(\) Type {`, }, } { diff --git a/internal/interface.go b/internal/interface.go deleted file mode 100644 index 6c5e8c921..000000000 --- a/internal/interface.go +++ /dev/null @@ -1,25 +0,0 @@ -package pkg - -import ( - "go/ast" - - "golang.org/x/tools/go/packages" -) - -type Interface struct { - Name string // Name of the type to be mocked. - FileName string - File *ast.File - Pkg *packages.Package - Config *Config -} - -func NewInterface(name string, filename string, file *ast.File, pkg *packages.Package, config *Config) *Interface { - return &Interface{ - Name: name, - FileName: filename, - File: file, - Pkg: pkg, - Config: config, - } -} diff --git a/internal/mockery_test.go b/internal/mockery_test.go index 7248ecf68..af1d558f7 100644 --- a/internal/mockery_test.go +++ b/internal/mockery_test.go @@ -1,4 +1,4 @@ -package pkg_test +package internal_test import ( "path/filepath" diff --git a/internal/node_visitor.go b/internal/node_visitor.go index 7ec1575e8..eb1ba1de9 100644 --- a/internal/node_visitor.go +++ b/internal/node_visitor.go @@ -1,4 +1,4 @@ -package pkg +package internal import ( "context" diff --git a/internal/parse.go b/internal/parse.go index 9401d881b..c0f380cef 100644 --- a/internal/parse.go +++ b/internal/parse.go @@ -1,4 +1,4 @@ -package pkg +package internal import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "github.com/rs/zerolog" + "github.com/vektra/mockery/v3/template" "golang.org/x/tools/go/packages" ) @@ -37,9 +38,9 @@ func NewParser(buildTags []string) *Parser { return p } -func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*Interface, error) { +func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*template.Interface, error) { log := zerolog.Ctx(ctx) - interfaces := []*Interface{} + interfaces := []*template.Interface{} packages, err := packages.Load(&p.conf, packageNames...) if err != nil { @@ -90,7 +91,7 @@ func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*I continue } - interfaces = append(interfaces, NewInterface( + interfaces = append(interfaces, template.NewInterface( name, file, fileSyntax, diff --git a/internal/template_generator.go b/internal/template_generator.go index ec2e8b763..93fd228cb 100644 --- a/internal/template_generator.go +++ b/internal/template_generator.go @@ -1,4 +1,4 @@ -package pkg +package internal import ( "bufio" @@ -24,9 +24,9 @@ import ( type Formatter string const ( - FORMAT_GOFMT Formatter = "gofmt" - FORMAT_GOIMPORRTS Formatter = "goimports" - FORMAT_NOOP Formatter = "noop" + FormatGofmt Formatter = "gofmt" + FormatGoImports Formatter = "goimports" + FormatNoop Formatter = "noop" ) var ( @@ -103,7 +103,7 @@ type TemplateGenerator struct { registry *template.Registry formatter Formatter inPackage bool - pkgConfig *Config + pkgConfig *template.Config pkgName string } @@ -113,7 +113,7 @@ func NewTemplateGenerator( outPkgFSPath *pathlib.Path, templateName string, formatter Formatter, - pkgConfig *Config, + pkgConfig *template.Config, pkgName string, ) (*TemplateGenerator, error) { srcPkgFSPath := pathlib.NewPath(srcPkg.GoFiles[0]).Parent() @@ -166,18 +166,18 @@ func NewTemplateGenerator( func (g *TemplateGenerator) format(src []byte) ([]byte, error) { switch g.formatter { - case FORMAT_GOIMPORRTS: + case FormatGoImports: return goimports(src) - case FORMAT_GOFMT: + case FormatGofmt: return gofmt(src) - case FORMAT_NOOP: + case FormatNoop: return src, nil } return nil, fmt.Errorf("unknown formatter type: %s", g.formatter) } -func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func) template.MethodData { +func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func, ifaceConfig *template.Config) (template.MethodData, error) { log := zerolog.Ctx(ctx) methodScope := g.registry.MethodScope() @@ -192,8 +192,34 @@ func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func) log.Debug().Str("import", imprt.Path()).Str("import-qualifier", imprt.Qualifier()).Msg("existing imports") } + var paramPkgPath string + var paramObjName string + switch t := param.Type().(type) { + case *types.Named: + pkg := t.Obj().Pkg() + if pkg != nil { + paramPkgPath = pkg.Path() + } + paramObjName = t.Obj().Name() + case *types.Alias: + pkg := t.Obj().Pkg() + if pkg != nil { + paramPkgPath = pkg.Path() + } + paramObjName = t.Obj().Name() + } + replacement := ifaceConfig.GetReplacement(paramPkgPath, paramObjName) + if replacement != nil { + log.Debug().Str("replace-to-pkg-path", replacement.PkgPath).Str("replace-to-type-name", replacement.TypeName).Msg("found replacement") + } else { + log.Debug().Str("param-pkg-path", paramPkgPath).Msg("replacement not found") + } + v, err := methodScope.AddVar(ctx, param, "", replacement) + if err != nil { + return template.MethodData{}, err + } params[j] = template.ParamData{ - Var: methodScope.AddVar(param, ""), + Var: v, Variadic: signature.Variadic() && j == signature.Params().Len()-1, } } @@ -201,8 +227,31 @@ func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func) returns := make([]template.ParamData, signature.Results().Len()) for j := 0; j < signature.Results().Len(); j++ { param := signature.Results().At(j) + + var paramPkgPath string + var paramObjName string + switch t := param.Type().(type) { + case *types.Named: + pkg := t.Obj().Pkg() + if pkg != nil { + paramPkgPath = pkg.Path() + } + paramObjName = t.Obj().Name() + case *types.Alias: + pkg := t.Obj().Pkg() + if pkg != nil { + paramPkgPath = pkg.Path() + } + paramObjName = t.Obj().Name() + } + + replacement := ifaceConfig.GetReplacement(paramPkgPath, paramObjName) + v, err := methodScope.AddVar(ctx, param, "", replacement) + if err != nil { + return template.MethodData{}, err + } returns[j] = template.ParamData{ - Var: methodScope.AddVar(param, ""), + Var: v, Variadic: false, } } @@ -211,7 +260,7 @@ func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func) Params: params, Returns: returns, Scope: methodScope, - } + }, nil } func explicitConstraintType(typeParam *types.Var) (t types.Type) { @@ -230,10 +279,10 @@ func explicitConstraintType(typeParam *types.Var) (t types.Type) { return nil } -func (g *TemplateGenerator) typeParams(ctx context.Context, tparams *types.TypeParamList) []template.TypeParamData { +func (g *TemplateGenerator) typeParams(ctx context.Context, tparams *types.TypeParamList) ([]template.TypeParamData, error) { var tpd []template.TypeParamData if tparams == nil { - return tpd + return tpd, nil } tpd = make([]template.TypeParamData, tparams.Len()) @@ -242,18 +291,22 @@ func (g *TemplateGenerator) typeParams(ctx context.Context, tparams *types.TypeP for i := 0; i < len(tpd); i++ { tp := tparams.At(i) typeParam := types.NewParam(token.Pos(i), tp.Obj().Pkg(), tp.Obj().Name(), tp.Constraint()) + v, err := scope.AddVar(ctx, typeParam, "", nil) + if err != nil { + return nil, err + } tpd[i] = template.TypeParamData{ - ParamData: template.ParamData{Var: scope.AddVar(typeParam, "")}, + ParamData: template.ParamData{Var: v}, Constraint: explicitConstraintType(typeParam), } } - return tpd + return tpd, nil } func (g *TemplateGenerator) Generate( ctx context.Context, - interfaces []*Interface, + interfaces []*template.Interface, ) ([]byte, error) { log := zerolog.Ctx(ctx) mockData := []template.MockData{} @@ -275,7 +328,11 @@ func (g *TemplateGenerator) Generate( methods := make([]template.MethodData, iface.NumMethods()) for i := 0; i < iface.NumMethods(); i++ { - methods[i] = g.methodData(ctx, iface.Method(i)) + methodData, err := g.methodData(ctx, iface.Method(i), ifaceMock.Config) + if err != nil { + return nil, err + } + methods[i] = methodData } // Now that all methods have been generated, we need to resolve naming // conflicts that arise between variable names and package qualifiers. @@ -290,10 +347,14 @@ func (g *TemplateGenerator) Generate( } ifaceLog.Debug().Str("template-data", fmt.Sprintf("%v", ifaceMock.Config.TemplateData)).Msg("printing template data") + tParams, err := g.typeParams(ctx, tparams) + if err != nil { + return nil, err + } mockData = append(mockData, template.MockData{ InterfaceName: ifaceMock.Name, MockName: *ifaceMock.Config.MockName, - TypeParams: g.typeParams(ctx, tparams), + TypeParams: tParams, Methods: methods, TemplateData: ifaceMock.Config.TemplateData, }) @@ -339,8 +400,6 @@ func (g *TemplateGenerator) Generate( } log.Debug().Msg("formatting file in-memory") - // TODO: Grabbing ifaceConfigs[0].Formatter doesn't make sense. We should instead - // grab the formatter as specified in the topmost interface-level config. formatted, err := g.format(buf.Bytes()) if err != nil { scanner := bufio.NewScanner(strings.NewReader(buf.String())) diff --git a/mkdocs.yml b/mkdocs.yml index 977c14eee..0985f4590 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,8 @@ nav: - Templates: - template-mockery.md - template-moq.md + - Features: + - replace-type.md - Notes: - FAQ: faq.md - v3: v3.md diff --git a/internal/config.go b/template/config.go similarity index 82% rename from internal/config.go rename to template/config.go index 123f8b915..a5d48ef95 100644 --- a/internal/config.go +++ b/template/config.go @@ -1,4 +1,4 @@ -package pkg +package template import ( "bufio" @@ -25,10 +25,49 @@ import ( "github.com/spf13/pflag" "github.com/vektra/mockery/v3/internal/logging" "github.com/vektra/mockery/v3/internal/stackerr" - mockeryTemplate "github.com/vektra/mockery/v3/template" "golang.org/x/tools/go/packages" ) +type Interface struct { + Name string // Name of the type to be mocked. + FileName string + File *ast.File + Pkg *packages.Package + Config *Config +} + +// ConfigData is the data sent to the template for the config file. +type ConfigData struct { + // ConfigDir is the directory of where the mockery config file is located. + ConfigDir string + // InterfaceDir is the directory of the interface being mocked. + InterfaceDir string + // InterfaceDirRelative is the same as InterfaceDir, but made relative to the ConfigDir. + InterfaceDirRelative string + // InterfaceFile is the filename of where the interface is defined. + InterfaceFile string + // InterfaceName is the name of the interface (duh). + InterfaceName string + // Mock is a parameter that takes the value of "Mock" if the interface is exported, and "mock" otherwise. + Mock string + // MockName is the configured name of the mock. + MockName string + // SrcPackageName is the name of the source package as defined by the `package [name]` in the source package. + SrcPackageName string + // SrcPackagePath is the fully qualified package path of the source package. e.g. "github.com/vektra/mockery/v3". + SrcPackagePath string +} + +func NewInterface(name string, filename string, file *ast.File, pkg *packages.Package, config *Config) *Interface { + return &Interface{ + Name: name, + FileName: filename, + File: file, + Pkg: pkg, + Config: config, + } +} + type RootConfig struct { *Config `koanf:",squash"` Packages map[string]*PackageConfig `koanf:"packages"` @@ -65,12 +104,19 @@ func NewRootConfig( } k := koanf.New("|") rootConfig.koanf = k - k.Set("dir", "{{.InterfaceDir}}") - k.Set("filename", "mocks_test.go") - k.Set("formatter", "goimports") - k.Set("mockname", "Mock{{.InterfaceName}}") - k.Set("pkgname", "{{.SrcPackageName}}") - k.Set("log-level", "info") + for key, val := range map[string]string{ + "dir": "{{.InterfaceDir}}", + "filename": "mocks_test.go", + "formatter": "goimports", + "mockname": "Mock{{.InterfaceName}}", + "pkgname": "{{.SrcPackageName}}", + "log-level": "info", + } { + if err := k.Set(key, val); err != nil { + log.Err(err).Msg("failed to set default value") + return nil, nil, stackerr.NewStackErr(err) + } + } configFileFromEnv := os.Getenv("MOCKERY_CONFIG") if configFileFromEnv != "" { @@ -94,7 +140,7 @@ func NewRootConfig( log.Debug().Str("config-file", configFile.String()).Msg("config file found") } rootConfig.configFile = configFile - k.Load( + if err := k.Load( env.Provider( "MOCKERY_", ".", @@ -102,7 +148,10 @@ func NewRootConfig( return strings.Replace(strings.ToLower(strings.TrimPrefix(s, "MOCKERY_")), "_", "-", -1) }), nil, - ) + ); err != nil { + log.Err(err).Msg("failed to load environment provider") + return nil, nil, stackerr.NewStackErr(err) + } if err := k.Load(file.Provider(configFile.String()), koanfYAML.Parser()); err != nil { return nil, k, fmt.Errorf("loading config file: %w", err) @@ -162,8 +211,16 @@ func mergeConfigs(ctx context.Context, src Config, dest *Config) { destFieldValue := destValue.Elem().Field(i) if srcFieldValue.Kind() == reflect.Map { - srcMap := srcFieldValue.Interface().(map[string]any) - destMap := destFieldValue.Interface().(map[string]any) + srcMap, ok := srcFieldValue.Interface().(map[string]any) + if !ok { + log.Debug().Msg("field value is not `any`, skipping merge") + continue + } + destMap, ok := destFieldValue.Interface().(map[string]any) + if !ok { + log.Debug().Msg("dest map value is not `any`, skipping") + continue + } if destMap == nil { destFieldValue.Set(reflect.ValueOf(make(map[string]any))) } @@ -403,6 +460,11 @@ func (c *InterfaceConfig) Initialize(ctx context.Context) error { return nil } +type ReplaceType struct { + PkgPath string `koanf:"pkg-path"` + TypeName string `koanf:"type-name"` +} + type Config struct { All *bool `koanf:"all"` Anchors map[string]any `koanf:"_anchors"` @@ -412,16 +474,20 @@ type Config struct { ExcludeSubpkgRegex []string `koanf:"exclude-subpkg-regex"` ExcludeRegex *string `koanf:"exclude-regex"` FileName *string `koanf:"filename"` - Formatter *string `koanf:"formatter"` - IncludeRegex *string `koanf:"include-regex"` - LogLevel *string `koanf:"log-level"` - MockName *string `koanf:"mockname"` - PkgName *string `koanf:"pkgname"` - Recursive *bool `koanf:"recursive"` - Template *string `koanf:"template"` - TemplateData map[string]any `koanf:"template-data"` - UnrollVariadic *bool `koanf:"unroll-variadic"` - Version *bool `koanf:"version"` + // ForceFileWrite controls whether mockery will overwrite existing files when generating mocks. This is by default set to false. + ForceFileWrite bool `koanf:"force-file-write"` + Formatter *string `koanf:"formatter"` + IncludeRegex *string `koanf:"include-regex"` + LogLevel *string `koanf:"log-level"` + MockName *string `koanf:"mockname"` + PkgName *string `koanf:"pkgname"` + Recursive *bool `koanf:"recursive"` + // ReplaceType is a nested map of format map["package path"]["type name"]*ReplaceType + ReplaceType map[string]map[string]*ReplaceType `koanf:"replace-type"` + Template *string `koanf:"template"` + TemplateData map[string]any `koanf:"template-data"` + UnrollVariadic *bool `koanf:"unroll-variadic"` + Version *bool `koanf:"version"` } func findConfig() (*pathlib.Path, error) { @@ -526,7 +592,7 @@ func (c *Config) ParseTemplates(ctx context.Context, iface *Interface, srcPkg *p } } // data is the struct sent to the template parser - data := mockeryTemplate.ConfigData{ + data := ConfigData{ ConfigDir: filepath.Dir(*c.ConfigFile), InterfaceDir: interfaceDir, InterfaceDirRelative: interfaceDirRelative, @@ -565,7 +631,7 @@ func (c *Config) ParseTemplates(ctx context.Context, iface *Interface, srcPkg *p for name, attributePointer := range templateMap { oldVal := *attributePointer - attributeTempl, err := template.New("config-template").Funcs(mockeryTemplate.StringManipulationFuncs).Parse(*attributePointer) + attributeTempl, err := template.New("config-template").Funcs(StringManipulationFuncs).Parse(*attributePointer) if err != nil { return fmt.Errorf("failed to parse %s template: %w", name, err) } @@ -583,3 +649,11 @@ func (c *Config) ParseTemplates(ctx context.Context, iface *Interface, srcPkg *p return nil } + +func (c *Config) GetReplacement(pkgPath string, typeName string) *ReplaceType { + pkgMap := c.ReplaceType[pkgPath] + if pkgMap == nil { + return nil + } + return pkgMap[typeName] +} diff --git a/template/method_scope.go b/template/method_scope.go index 38857005d..669fd1b91 100644 --- a/template/method_scope.go +++ b/template/method_scope.go @@ -6,6 +6,8 @@ import ( "go/types" "github.com/rs/zerolog" + "github.com/vektra/mockery/v3/internal/stackerr" + "golang.org/x/tools/go/packages" ) // MethodScope is the sub-registry for allocating variables present in @@ -73,25 +75,108 @@ func (m *MethodScope) AllocateName(prefix string) string { return suggestion } +// fakePackage is used during type replacements (the replace-type parameter). +// We don't want to call `packages.Load` in order to obtain a real `*packages.Package` +// object, so we instead can create a mock implementation and provide the necessary +// values (obtained from `replace-type`). +type fakePackage struct { + name string + path string +} + +func (f fakePackage) Name() string { + return f.name +} + +func (f fakePackage) Path() string { + return f.path +} + +var _ TypesPackage = fakePackage{} + // AddVar allocates a variable instance and adds it to the method scope. // // Variables names are generated if required and are ensured to be // without conflict with other variables and imported packages. It also // adds the relevant imports to the registry for each added variable. -func (m *MethodScope) AddVar(vr *types.Var, prefix string) *Var { - imports := m.populateImports(context.Background(), vr.Type()) - v := Var{ - vr: vr, - imports: imports, - moqPkgPath: m.moqPkgPath, - } - // The variable type is also a visible name, so add that. - m.AddName(v.TypeString()) +func (m *MethodScope) AddVar(ctx context.Context, vr *types.Var, prefix string, replacement *ReplaceType) (*Var, error) { + var ( + imports map[string]*Package = map[string]*Package{} + v Var + ) - v.Name = m.AllocateName(varName(vr, prefix)) + log := zerolog.Ctx(ctx) + if replacement != nil { + newLogger := log.With(). + Str("replace-pkg-path", replacement.PkgPath). + Str("replace-type-name", replacement.TypeName).Logger() + log = &newLogger + ctx = log.WithContext(ctx) + log.Debug().Msg("working with replacement") + + // Type replacements are really tricky. Mockery needs to correctly + // gather type information from the package specified in the replacement. + // This basically means that we need to call packages.Load to satisfy this requirement, + // then find the type name in the replacement. + // + // NOTE: This section WILL be slow, because `packages.Load` is slow. Future + // enhancement will be to find a way to either cache these calls, batch + // them together for all replace-type instances, or find a way to avoid + // this altogether. + var conf packages.Config + conf.Mode = packages.NeedTypes | + packages.NeedTypesSizes | + packages.NeedSyntax | + packages.NeedTypesInfo | + packages.NeedImports | + packages.NeedName | + packages.NeedFiles | + packages.NeedCompiledGoFiles + pkgs, err := packages.Load(&conf, replacement.PkgPath) + if err != nil { + log.Err(err).Msg("couldn't load package") + return nil, stackerr.NewStackErr(err) + } + var object types.Object + var objectPkg *packages.Package + for _, pkg := range pkgs { + object = pkg.Types.Scope().Lookup(replacement.TypeName) + if object != nil { + objectPkg = pkg + break + } + } + if object == nil { + log.Error().Msg("type-name was not found in the referenced package") + return nil, stackerr.NewStackErr(fmt.Errorf("type does not exist in referenced package")) + } + + m.addImport( + ctx, + objectPkg.Types, + imports, + ) + v = Var{ + vr: vr, + typ: object.Type(), + imports: imports, + moqPkgPath: m.moqPkgPath, + } + } else { + //nolint:contextcheck + imports = m.populateImports(context.Background(), vr.Type()) + v = Var{ + vr: vr, + typ: vr.Type(), + imports: imports, + moqPkgPath: m.moqPkgPath, + } + m.AddName(v.TypeString()) + } + v.Name = m.AllocateName(varName(vr, prefix)) m.vars = append(m.vars, &v) - return &v + return &v, nil } // AddName records name as visible in the current scope. This may be useful @@ -107,7 +192,7 @@ func (m *MethodScope) NameExists(name string) bool { return exists } -func (m *MethodScope) addImport(ctx context.Context, pkg *types.Package, imports map[string]*Package) { +func (m *MethodScope) addImport(ctx context.Context, pkg TypesPackage, imports map[string]*Package) { imprt := m.registry.AddImport(ctx, pkg) imports[pkg.Path()] = imprt m.imports[pkg.Path()] = imprt diff --git a/template/package.go b/template/package.go index 2baf441fa..f7be4f3ec 100644 --- a/template/package.go +++ b/template/package.go @@ -71,13 +71,6 @@ func (p Package) uniqueName(lvl int) string { return name } -func min(a, b int) int { - if a < b { - return a - } - return b -} - func reverse(a []string) { for i := len(a)/2 - 1; i >= 0; i-- { opp := len(a) - 1 - i diff --git a/template/registry.go b/template/registry.go index f7e5e4aee..e165d5972 100644 --- a/template/registry.go +++ b/template/registry.go @@ -83,7 +83,7 @@ func (r *Registry) MethodScope() *MethodScope { // AddImport adds the given package to the set of imports. It generates a // suitable alias if there are any conflicts with previously imported // packages. -func (r *Registry) AddImport(ctx context.Context, pkg *types.Package) *Package { +func (r *Registry) AddImport(ctx context.Context, pkg TypesPackage) *Package { path := pkg.Path() log := zerolog.Ctx(ctx).With(). Str("method", "AddImport"). diff --git a/template/template.go b/template/template.go index c1d4125fb..863f4c733 100644 --- a/template/template.go +++ b/template/template.go @@ -116,6 +116,7 @@ var TemplateMockFuncs = template.FuncMap{ }, } +//nolint:predeclared var StringManipulationFuncs = template.FuncMap{ // String inspection and manipulation. Note that the first argument is replaced // as the last argument in some functions in order to support chained diff --git a/template/template_data.go b/template/template_data.go index d5ee938f6..e3b012e5c 100644 --- a/template/template_data.go +++ b/template/template_data.go @@ -6,19 +6,6 @@ import ( "strings" ) -// ConfigData is the data sent to the template for the config file. -type ConfigData struct { - ConfigDir string - InterfaceDir string - InterfaceDirRelative string - InterfaceFile string - InterfaceName string - Mock string - MockName string - SrcPackageName string - SrcPackagePath string -} - // Data is the template data used to render the mock template. type Data struct { PkgName string diff --git a/template/var.go b/template/var.go index 24f42bc90..fc8b677cb 100644 --- a/template/var.go +++ b/template/var.go @@ -9,7 +9,12 @@ import ( // // It should be created using a method scope instance. type Var struct { - vr *types.Var + vr *types.Var + // typ is stored separately from `vr.Type()` because it's possible + // for a variable to be replaced with another variable via replace-type. + // In such a case, `vr.Type()` refers to the original type and `typ` refers + // to the replacer type. + typ types.Type imports map[string]*Package moqPkgPath string @@ -17,19 +22,19 @@ type Var struct { } func (v Var) Type() types.Type { - return v.vr.Type() + return v.typ } // IsSlice returns whether the type (or the underlying type) is a slice. func (v Var) IsSlice() bool { - _, ok := v.vr.Type().Underlying().(*types.Slice) + _, ok := v.Type().Underlying().(*types.Slice) return ok } // TypeString returns the variable type with the package qualifier in the // format 'pkg.Type'. func (v Var) TypeString() string { - return types.TypeString(v.vr.Type(), v.packageQualifier) + return types.TypeString(v.Type(), v.packageQualifier) } // packageQualifier is a types.Qualifier. @@ -52,7 +57,7 @@ func nillable(typ types.Type) bool { } func (v Var) Nillable() bool { - return nillable(v.vr.Type()) + return nillable(v.Type()) } func varName(vr *types.Var, suffix string) string { diff --git a/tools/tools.go b/tools/tools.go index 4a5cd1397..4a7699110 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -6,4 +6,5 @@ import ( _ "github.com/go-task/task/v3/cmd/task" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "gotest.tools/gotestsum" + _ "mvdan.cc/gofumpt" )