diff --git a/docs/configuration.md b/docs/configuration.md index cf391311..473b9b02 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,9 +67,10 @@ Parameter Descriptions | `disable-version-string` | :fontawesome-solid-x: | `#!yaml false` | Disable the version string in the generated mock files. | | `dry-run` | :fontawesome-solid-x: | `#!yaml false` | Print the actions that would be taken, but don't perform the actions. | | `exclude` | :fontawesome-solid-x: | `#!yaml []` | Specify subpackages to exclude when using `#!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. | | `include-auto-generated` | :fontawesome-solid-x: | `#!yaml true` | Set to `#!yaml false` if you need mockery to skip auto-generated files during its recursive package discovery. When set to `#!yaml true`, mockery includes auto-generated files when determining if a particular directory is an importable package. | -| `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 | +| `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`. | | `inpackage` | :fontawesome-solid-x: | `#!yaml false` | When generating mocks alongside the original interfaces, you must specify `inpackage: True` to inform mockery that the mock is being placed in the same package as the original interface. | | `mockname` | :fontawesome-solid-check: | `#!yaml "Mock{{.InterfaceName}}"` | The name of the generated mock. | | `outpkg` | :fontawesome-solid-check: | `#!yaml "{{.PackageName}}"` | Use `outpkg` to specify the package name of the generated mocks. | diff --git a/docs/features.md b/docs/features.md index 748fc44c..979b03b6 100644 --- a/docs/features.md +++ b/docs/features.md @@ -193,8 +193,21 @@ packages: include-regex: ".*Client" ``` +To further refine matched interfaces, you can also use `exclude-regex`. If an interface matches both `include-regex` and `exclude-regex` then it will not be generated. For example, to generate all interfaces except those ending in `Func`: + +```yaml +packages: + github.com/user/project: + config: + recursive: true + include-regex: ".*" + exclude-regex: ".*Func" +``` + +You can only use `exclude-regex` with `include-regex`. If set by itself, `exclude-regex` has no effect. + ??? note "all: true" - Using `all: true` will override `include-regex` and issue a warning. + Using `all: true` will override `include-regex` (and `exclude-regex`) and issue a warning. Mock Constructors ----------------- diff --git a/pkg/config/config.go b/pkg/config/config.go index 86439929..2b4f205b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -41,6 +41,7 @@ type Config struct { DisableConfigSearch bool `mapstructure:"disable-config-search"` DisableVersionString bool `mapstructure:"disable-version-string"` DryRun bool `mapstructure:"dry-run"` + ExcludeRegex string `mapstructure:"exclude-regex"` Exported bool `mapstructure:"exported"` FileName string `mapstructure:"filename"` IncludeAutoGenerated bool `mapstructure:"include-auto-generated"` @@ -271,19 +272,37 @@ func (c *Config) ShouldGenerateInterface(ctx context.Context, packageName, inter } _, interfaceExists := interfacesSection[interfaceName] - var matchedByRegex bool + matchedByRegex := false if pkgConfig.IncludeRegex != "" { if pkgConfig.All { log := zerolog.Ctx(ctx) - log.Warn().Msg("interface config has both `all` and `include-regex` set. `include-regex` will be ignored") + log.Warn().Msg("interface config has both `all` and `include-regex` set: `include-regex` will be ignored") } else { matchedByRegex, err = regexp.MatchString(pkgConfig.IncludeRegex, interfaceName) if err != nil { - return false, err + return false, fmt.Errorf("evaluating `include-regex`: %w", err) } } } - return pkgConfig.All || interfaceExists || matchedByRegex, nil + excludedByRegex := false + if pkgConfig.ExcludeRegex != "" { + if pkgConfig.All { + log := zerolog.Ctx(ctx) + log.Warn().Msg("interface config has both `all` and `exclude-regex` set: `exclude-regex` will be ignored") + } else if pkgConfig.IncludeRegex == "" { + log := zerolog.Ctx(ctx) + log.Warn().Msg("interface config has `exclude-regex` set but not `include-regex`: `exclude-regex` will be ignored") + } else { + excludedByRegex, err = regexp.MatchString(pkgConfig.ExcludeRegex, interfaceName) + if err != nil { + return false, fmt.Errorf("evaluating `exclude-regex`: %w", err) + } + if excludedByRegex { + matchedByRegex = false + } + } + } + return pkgConfig.All || interfaceExists || (matchedByRegex && !excludedByRegex), nil } func (c *Config) getInterfacesSection(ctx context.Context, packageName string) (map[string]any, error) { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 2e151e07..b5758052 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -609,7 +609,7 @@ func TestConfig_ShouldGenerateInterface(t *testing.T) { want: true, }, { - name: "should generate using included-regex", + name: "should generate using include-regex", c: &Config{ Packages: map[string]interface{}{ "some_package": map[string]interface{}{ @@ -622,7 +622,7 @@ func TestConfig_ShouldGenerateInterface(t *testing.T) { want: true, }, { - name: "should generate when using all and included-regex doesn't match", + name: "should generate when using all and include-regex doesn't match", c: &Config{ Packages: map[string]interface{}{ "some_package": map[string]interface{}{ @@ -636,7 +636,7 @@ func TestConfig_ShouldGenerateInterface(t *testing.T) { want: true, }, { - name: "should not generate when included-regex doesn't match", + name: "should not generate when include-regex doesn't match", c: &Config{ Packages: map[string]interface{}{ "some_package": map[string]interface{}{ @@ -648,6 +648,91 @@ func TestConfig_ShouldGenerateInterface(t *testing.T) { }, want: false, }, + { + name: "should not generate when include-regex and exclude-regex both match", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "config": map[string]interface{}{ + "include-regex": ".*Interface", + "exclude-regex": "Some.*", + }, + }, + }, + }, + want: false, + }, + { + name: "should generate when include-regex matches but not exclude-regex", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "config": map[string]interface{}{ + "include-regex": ".*Interface", + "exclude-regex": "Foo.*", + }, + }, + }, + }, + want: true, + }, + { + name: "should not generate when neither include-regex nor exclude-regex match", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "config": map[string]interface{}{ + "include-regex": ".*XInterface", + "exclude-regex": "Foo.*", + }, + }, + }, + }, + want: false, + }, + { + name: "should not generate when exclude-regex doesn't match but include-regex isn't set", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "config": map[string]interface{}{ + "exclude-regex": "Foo.*", + }, + }, + }, + }, + want: false, + }, + { + name: "should generate when using all and exclude-regex matches", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "config": map[string]interface{}{ + "all": true, + "exclude-regex": ".*Interface", + }, + }, + }, + }, + want: true, + }, + { + name: "should generate when interface is selected and exclude-regex matches", + c: &Config{ + Packages: map[string]interface{}{ + "some_package": map[string]interface{}{ + "interfaces": map[string]interface{}{ + "SomeInterface": struct{}{}, + }, + "config": map[string]interface{}{ + "exclude-regex": ".*Interface", + }, + }, + }, + }, + want: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {