Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion cmd/wrapcheck/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reportLocalModule: false
3 changes: 3 additions & 0 deletions wrapcheck/testdata/config_reportLocalModule/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example.com/reportlocal

go 1.24
7 changes: 7 additions & 0 deletions wrapcheck/testdata/config_reportLocalModule/local/local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package local

import "errors"

func Error() error {
return errors.New("failed")
}
15 changes: 15 additions & 0 deletions wrapcheck/testdata/config_reportLocalModule/main.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reportLocalModule: true
3 changes: 3 additions & 0 deletions wrapcheck/testdata/config_reportLocalModule_true/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example.com/reportlocal

go 1.24
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package local

import "errors"

func Error() error {
return errors.New("failed")
}
15 changes: 15 additions & 0 deletions wrapcheck/testdata/config_reportLocalModule_true/main.go
Original file line number Diff line number Diff line change
@@ -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
}
33 changes: 32 additions & 1 deletion wrapcheck/wrapcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -95,6 +101,7 @@ func NewDefaultConfig() WrapcheckConfig {
IgnoreSigRegexps: []string{},
IgnorePackageGlobs: []string{},
IgnoreInterfaceRegexps: []string{},
ReportLocalModule: true,
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion wrapcheck/wrapcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down