|
1 | | -// package config defines the schemas and functionality of the .mockery.yml |
| 1 | +// Package config defines the schemas and functionality of the .mockery.yml |
2 | 2 | // config files. This package is NOT meant to be used by external Go libraries. |
3 | 3 | // We expose the contents of this package purely for documentation purposes. |
4 | 4 | // |
@@ -34,6 +34,7 @@ import ( |
34 | 34 | "github.com/vektra/mockery/v3/internal/stackerr" |
35 | 35 | "github.com/vektra/mockery/v3/template_funcs" |
36 | 36 | "golang.org/x/tools/go/packages" |
| 37 | + "gopkg.in/yaml.v3" |
37 | 38 | ) |
38 | 39 |
|
39 | 40 | // TemplateData is the data sent to the template for the config file. |
@@ -72,6 +73,7 @@ func NewDefaultKoanf(ctx context.Context) (*koanf.Koanf, error) { |
72 | 73 | FileName: addr("mocks_test.go"), |
73 | 74 | ForceFileWrite: addr(true), |
74 | 75 | Formatter: addr("goimports"), |
| 76 | + Generate: addr(true), |
75 | 77 | IncludeAutoGenerated: addr(false), |
76 | 78 | LogLevel: addr("info"), |
77 | 79 | StructName: addr("{{.Mock}}{{.InterfaceName}}"), |
@@ -418,26 +420,71 @@ func (c *PackageConfig) Initialize(ctx context.Context) error { |
418 | 420 | return nil |
419 | 421 | } |
420 | 422 |
|
421 | | -func (c PackageConfig) GetInterfaceConfig(ctx context.Context, interfaceName string) *InterfaceConfig { |
422 | | - log := zerolog.Ctx(ctx) |
| 423 | +func (c PackageConfig) GetInterfaceConfig(ctx context.Context, interfaceName string, directiveConfig *Config) (*InterfaceConfig, error) { |
| 424 | + // If the interface has an explicit config, override it with the directive config. |
| 425 | + // This favor any config set in the directive comment over the original file based config. |
423 | 426 | if ifaceConfig, ok := c.Interfaces[interfaceName]; ok { |
424 | | - return ifaceConfig |
| 427 | + if directiveConfig != nil { |
| 428 | + newConfig, err := deep.Copy(directiveConfig) |
| 429 | + if err != nil { |
| 430 | + return nil, fmt.Errorf("cloning directive config: %w", err) |
| 431 | + } |
| 432 | + |
| 433 | + // Merge the interface config into the directive config clone. |
| 434 | + mergeConfigs(ctx, *ifaceConfig.Config, newConfig) |
| 435 | + |
| 436 | + ifaceConfig.Config = newConfig |
| 437 | + |
| 438 | + for i, subCfg := range ifaceConfig.Configs { |
| 439 | + newConfig, err := deep.Copy(directiveConfig) |
| 440 | + if err != nil { |
| 441 | + return nil, fmt.Errorf("cloning directive config: %w", err) |
| 442 | + } |
| 443 | + |
| 444 | + // Merge the interface config into the directive config clone. |
| 445 | + mergeConfigs(ctx, *subCfg, newConfig) |
| 446 | + ifaceConfig.Configs[i] = newConfig |
| 447 | + } |
| 448 | + } |
| 449 | + |
| 450 | + return ifaceConfig, nil |
425 | 451 | } |
| 452 | + |
| 453 | + // We don't have a specific config for this interface, |
| 454 | + // we should create a new one. |
426 | 455 | ifaceConfig := NewInterfaceConfig() |
427 | 456 |
|
428 | | - newConfig, err := deep.Copy(c.Config) |
429 | | - if err != nil { |
430 | | - log.Err(err).Msg("issue when deep-copying package config to interface config") |
431 | | - panic(err) |
| 457 | + // If there is a directive config, use it as the base config. |
| 458 | + if directiveConfig != nil { |
| 459 | + newConfig, err := deep.Copy(directiveConfig) |
| 460 | + if err != nil { |
| 461 | + return nil, fmt.Errorf("cloning directive config: %w", err) |
| 462 | + } |
| 463 | + ifaceConfig.Config = newConfig |
432 | 464 | } |
433 | 465 |
|
434 | | - ifaceConfig.Config = newConfig |
435 | | - ifaceConfig.Configs = []*Config{newConfig} |
436 | | - return ifaceConfig |
| 466 | + // Finally, merge the package config into the new config |
| 467 | + mergeConfigs(ctx, *c.Config, ifaceConfig.Config) |
| 468 | + |
| 469 | + ifaceConfig.Configs = []*Config{ifaceConfig.Config} |
| 470 | + return ifaceConfig, nil |
437 | 471 | } |
438 | 472 |
|
439 | | -func (c PackageConfig) ShouldGenerateInterface(ctx context.Context, interfaceName string) (bool, error) { |
| 473 | +func (c PackageConfig) ShouldGenerateInterface( |
| 474 | + ctx context.Context, |
| 475 | + interfaceName string, |
| 476 | + ifaceConfig Config, |
| 477 | + hasDirectiveComment bool, |
| 478 | +) (bool, error) { |
440 | 479 | log := zerolog.Ctx(ctx) |
| 480 | + if hasDirectiveComment { |
| 481 | + if ifaceConfig.Generate != nil && !*ifaceConfig.Generate { |
| 482 | + log.Debug().Msg("interface has directive comment with generate: false, skipping generation") |
| 483 | + return false, nil |
| 484 | + } |
| 485 | + log.Debug().Msg("interface has directive comment, generating mock") |
| 486 | + return true, nil |
| 487 | + } |
441 | 488 | if *c.Config.All { |
442 | 489 | if *c.Config.IncludeInterfaceRegex != "" { |
443 | 490 | log.Warn().Msg("interface config has both `all` and `include-interface-regex` set: `include-interface-regex` will be ignored") |
@@ -526,6 +573,7 @@ type Config struct { |
526 | 573 | // ForceFileWrite controls whether mockery will overwrite existing files when generating mocks. This is by default set to false. |
527 | 574 | ForceFileWrite *bool `koanf:"force-file-write" yaml:"force-file-write,omitempty"` |
528 | 575 | Formatter *string `koanf:"formatter" yaml:"formatter,omitempty"` |
| 576 | + Generate *bool `koanf:"generate" yaml:"generate,omitempty"` |
529 | 577 | IncludeAutoGenerated *bool `koanf:"include-auto-generated" yaml:"include-auto-generated,omitempty"` |
530 | 578 | IncludeInterfaceRegex *string `koanf:"include-interface-regex" yaml:"include-interface-regex,omitempty"` |
531 | 579 | InPackage *bool `koanf:"inpackage" yaml:"inpackage,omitempty"` |
@@ -695,3 +743,40 @@ func (c *Config) GetReplacement(pkgPath string, typeName string) *ReplaceType { |
695 | 743 | } |
696 | 744 | return pkgMap[typeName] |
697 | 745 | } |
| 746 | + |
| 747 | +// ExtractDirectiveConfig parses interface's documentation from a declaration |
| 748 | +// node and extracts mockery's directive configuration. |
| 749 | +// |
| 750 | +// Mockery directives are comments that start with "mockery:" and can appear |
| 751 | +// multiple times in the interface's doc comments. All such comments are combined |
| 752 | +// and interpreted as YAML configuration. |
| 753 | +func ExtractDirectiveConfig(ctx context.Context, decl *ast.GenDecl) (*Config, error) { |
| 754 | + if decl == nil || decl.Doc == nil { |
| 755 | + return nil, nil |
| 756 | + } |
| 757 | + |
| 758 | + var yamlConfig []string |
| 759 | + |
| 760 | + // Extract all mockery directive comments and build a YAML document |
| 761 | + for _, doc := range decl.Doc.List { |
| 762 | + // Look for directive comments `//mockery:<config-key>: <value>` and convert them to YAML |
| 763 | + if value, found := strings.CutPrefix(doc.Text, "//mockery:"); found && value != "" { |
| 764 | + yamlConfig = append(yamlConfig, value) |
| 765 | + } |
| 766 | + } |
| 767 | + |
| 768 | + if len(yamlConfig) == 0 { |
| 769 | + return nil, nil |
| 770 | + } |
| 771 | + |
| 772 | + // Combine all YAML lines into a single document |
| 773 | + yamlDoc := strings.Join(yamlConfig, "\n") |
| 774 | + |
| 775 | + // Parse the YAML directly into the directiveConfig struct |
| 776 | + directiveConfig := Config{} |
| 777 | + if err := yaml.Unmarshal([]byte(yamlDoc), &directiveConfig); err != nil { |
| 778 | + return nil, fmt.Errorf("unmarshaling directive yaml: %w", err) |
| 779 | + } |
| 780 | + |
| 781 | + return &directiveConfig, nil |
| 782 | +} |
0 commit comments