From 42a425fc428d4e3df75c34999575cec70be3f91f Mon Sep 17 00:00:00 2001 From: wreulicke <12907474+wreulicke@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:12:00 +0900 Subject: [PATCH] feat: add reportLocalModule option --- README.md | 5 +++ cmd/wrapcheck/main.go | 3 +- .../config_reportLocalModule/.wrapcheck.yaml | 1 + .../testdata/config_reportLocalModule/go.mod | 3 ++ .../config_reportLocalModule/local/local.go | 7 ++++ .../testdata/config_reportLocalModule/main.go | 15 +++++++++ .../.wrapcheck.yaml | 1 + .../config_reportLocalModule_true/go.mod | 3 ++ .../local/local.go | 7 ++++ .../config_reportLocalModule_true/main.go | 15 +++++++++ wrapcheck/wrapcheck.go | 33 ++++++++++++++++++- wrapcheck/wrapcheck_test.go | 6 +++- 12 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 wrapcheck/testdata/config_reportLocalModule/.wrapcheck.yaml create mode 100644 wrapcheck/testdata/config_reportLocalModule/go.mod create mode 100644 wrapcheck/testdata/config_reportLocalModule/local/local.go create mode 100644 wrapcheck/testdata/config_reportLocalModule/main.go create mode 100644 wrapcheck/testdata/config_reportLocalModule_true/.wrapcheck.yaml create mode 100644 wrapcheck/testdata/config_reportLocalModule_true/go.mod create mode 100644 wrapcheck/testdata/config_reportLocalModule_true/local/local.go create mode 100644 wrapcheck/testdata/config_reportLocalModule_true/main.go diff --git a/README.md b/README.md index 48cd472..b492106 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,11 @@ ignoreInterfaceRegexps: # ReportInternalErrors determines whether wrapcheck should report errors returned # from inside the package. reportInternalErrors: true + +# ReportLocalModule determines whether wrapcheck should report errors returned +# from packages within the module being analyzed. The package currently being +# analyzed is controlled separately by reportInternalErrors. +reportLocalModule: true ``` ## Usage diff --git a/cmd/wrapcheck/main.go b/cmd/wrapcheck/main.go index 006e398..4f1d53c 100644 --- a/cmd/wrapcheck/main.go +++ b/cmd/wrapcheck/main.go @@ -15,6 +15,7 @@ func main() { viper.AddConfigPath(".") viper.SetDefault("ignoreSigs", wrapcheck.DefaultIgnoreSigs) + viper.SetDefault("reportLocalModule", true) // Read in config, ignore if the file isn't found and use defaults. if err := viper.ReadInConfig(); err != nil { @@ -23,7 +24,7 @@ func main() { } } - var cfg wrapcheck.WrapcheckConfig + cfg := wrapcheck.NewDefaultConfig() if err := viper.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } diff --git a/wrapcheck/testdata/config_reportLocalModule/.wrapcheck.yaml b/wrapcheck/testdata/config_reportLocalModule/.wrapcheck.yaml new file mode 100644 index 0000000..886eb5d --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule/.wrapcheck.yaml @@ -0,0 +1 @@ +reportLocalModule: false diff --git a/wrapcheck/testdata/config_reportLocalModule/go.mod b/wrapcheck/testdata/config_reportLocalModule/go.mod new file mode 100644 index 0000000..1aa46f1 --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule/go.mod @@ -0,0 +1,3 @@ +module example.com/reportlocal + +go 1.24 diff --git a/wrapcheck/testdata/config_reportLocalModule/local/local.go b/wrapcheck/testdata/config_reportLocalModule/local/local.go new file mode 100644 index 0000000..463427c --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule/local/local.go @@ -0,0 +1,7 @@ +package local + +import "errors" + +func Error() error { + return errors.New("failed") +} diff --git a/wrapcheck/testdata/config_reportLocalModule/main.go b/wrapcheck/testdata/config_reportLocalModule/main.go new file mode 100644 index 0000000..98b7248 --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule/main.go @@ -0,0 +1,15 @@ +package main + +import "example.com/reportlocal/local" + +func main() { + _ = do() +} + +func do() error { + if err := local.Error(); err != nil { + return err + } + + return nil +} diff --git a/wrapcheck/testdata/config_reportLocalModule_true/.wrapcheck.yaml b/wrapcheck/testdata/config_reportLocalModule_true/.wrapcheck.yaml new file mode 100644 index 0000000..56d0d27 --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule_true/.wrapcheck.yaml @@ -0,0 +1 @@ +reportLocalModule: true diff --git a/wrapcheck/testdata/config_reportLocalModule_true/go.mod b/wrapcheck/testdata/config_reportLocalModule_true/go.mod new file mode 100644 index 0000000..1aa46f1 --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule_true/go.mod @@ -0,0 +1,3 @@ +module example.com/reportlocal + +go 1.24 diff --git a/wrapcheck/testdata/config_reportLocalModule_true/local/local.go b/wrapcheck/testdata/config_reportLocalModule_true/local/local.go new file mode 100644 index 0000000..463427c --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule_true/local/local.go @@ -0,0 +1,7 @@ +package local + +import "errors" + +func Error() error { + return errors.New("failed") +} diff --git a/wrapcheck/testdata/config_reportLocalModule_true/main.go b/wrapcheck/testdata/config_reportLocalModule_true/main.go new file mode 100644 index 0000000..89e3cb9 --- /dev/null +++ b/wrapcheck/testdata/config_reportLocalModule_true/main.go @@ -0,0 +1,15 @@ +package main + +import "example.com/reportlocal/local" + +func main() { + _ = do() +} + +func do() error { + if err := local.Error(); err != nil { + return err // want `error returned from external package is unwrapped` + } + + return nil +} diff --git a/wrapcheck/wrapcheck.go b/wrapcheck/wrapcheck.go index 12cd156..6228612 100644 --- a/wrapcheck/wrapcheck.go +++ b/wrapcheck/wrapcheck.go @@ -87,6 +87,12 @@ type WrapcheckConfig struct { // ReportInternalErrors determines whether wrapcheck should report errors returned // from inside the package. ReportInternalErrors bool `mapstructure:"reportInternalErrors" yaml:"reportInternalErrors"` + + // ReportLocalModule determines whether wrapcheck should report errors returned + // from packages within the module being analyzed. The package currently being + // analyzed is controlled separately by ReportInternalErrors. It defaults to + // true. + ReportLocalModule bool `mapstructure:"reportLocalModule" yaml:"reportLocalModule"` } func NewDefaultConfig() WrapcheckConfig { @@ -95,6 +101,7 @@ func NewDefaultConfig() WrapcheckConfig { IgnoreSigRegexps: []string{}, IgnorePackageGlobs: []string{}, IgnoreInterfaceRegexps: []string{}, + ReportLocalModule: true, } } @@ -298,7 +305,15 @@ func reportUnwrapped( return } - fnSig := pass.TypesInfo.ObjectOf(sel.Sel).String() + fn := pass.TypesInfo.ObjectOf(sel.Sel) + fnSig := fn.String() + + // Packages in the current module are not external packages. Keep this + // separate from ReportInternalErrors, which controls the package currently + // being analyzed. + if !cfg.ReportLocalModule && isFromOtherPackageInLocalModule(pass, fn.Pkg()) { + return + } // Check for ignored signatures if checkSignature(cfg, regexpsSig, fnSig) { @@ -363,6 +378,22 @@ func isFromOtherPkg(pass *analysis.Pass, sel *ast.SelectorExpr, pkgGlobs []glob. return true } +// isFromOtherPackageInLocalModule reports whether pkg belongs to the module +// currently being analyzed, but is not the package currently being analyzed. +// Package paths are hierarchical, so the module path must be followed by a +// slash when checking subpackages. +func isFromOtherPackageInLocalModule(pass *analysis.Pass, pkg *types.Package) bool { + if pass.Module == nil || pass.Module.Path == "" || pass.Pkg == nil || pkg == nil { + return false + } + + if pkg.Path() == pass.Pkg.Path() { + return false + } + + return pkg.Path() == pass.Module.Path || strings.HasPrefix(pkg.Path(), pass.Module.Path+"/") +} + // prevErrAssign traverses the AST of a file looking for the most recent // assignment to an error declaration which is specified by the returnIdent // identifier. diff --git a/wrapcheck/wrapcheck_test.go b/wrapcheck/wrapcheck_test.go index 1f76747..6100352 100644 --- a/wrapcheck/wrapcheck_test.go +++ b/wrapcheck/wrapcheck_test.go @@ -16,6 +16,10 @@ import ( // analysis tests to skip this directory due to needing explicit tests. const skipfile = "analysistest_skip" +func TestNewDefaultConfig(t *testing.T) { + assert.True(t, NewDefaultConfig().ReportLocalModule) +} + func TestAnalyzer(t *testing.T) { // Load the dirs under ./testdata p, err := filepath.Abs("./testdata") @@ -48,7 +52,7 @@ func TestAnalyzer(t *testing.T) { configFile, err := os.ReadFile(configPath) assert.NoError(t, err) - var config WrapcheckConfig + config := NewDefaultConfig() assert.NoError(t, yaml.Unmarshal(configFile, &config)) analysistest.Run(t, dirPath, NewAnalyzer(config)) } else {