Skip to content

Commit 208ecbc

Browse files
committed
forbidigo: better support for configuring complex rules
forbidigo 1.4.0 introduced a new configuration mechanism with multiple fields per rule. It still supports a single string, but allowing structs as alternative to such strings makes the configuration easier to read and write. A new global flag is needed to enable the more advanced analysis.
1 parent defde13 commit 208ecbc

File tree

9 files changed

+130
-16
lines changed

9 files changed

+130
-16
lines changed

.golangci.reference.yml

+20-4
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,30 @@ linters-settings:
358358
# Forbid the following identifiers (list of regexp).
359359
# Default: ["^(fmt\\.Print(|f|ln)|print|println)$"]
360360
forbid:
361+
# Builtin function:
361362
- ^print.*$
362-
- 'fmt\.Print.*'
363-
# Optionally put comments at the end of the regex, surrounded by `(# )?`
364-
# Escape any special characters.
363+
# Optional message that gets included in error reports.
364+
- pattern: ^fmt\.Print.*$
365+
msg: Do not commit print statements.
366+
# Alternatively, put messages at the end of the regex, surrounded by `(# )?`
367+
# Escape any special characters. Those messages get included in error reports.
365368
- 'fmt\.Print.*(# Do not commit print statements\.)?'
369+
# Forbid spew Dump, whether it is called as function or method.
370+
# Depends on expand_expression below.
371+
- ^spew\.(ConfigState\.)?Dump$
372+
# The package name in expanded expressions might be ambiguous.
373+
# The full import path can be used as additional criteria.
374+
- pattern: ^v1.Dump$
375+
pkg: ^example.com/pkg/api/v1$
366376
# Exclude godoc examples from forbidigo checks.
367377
# Default: true
368-
exclude_godoc_examples: false
378+
exclude-godoc-examples: false
379+
# Instead of matching the literal source code, use type information to
380+
# replace expressions with strings that contain the package name and (for
381+
# methods and fields) the type name. This makes it possible to handle
382+
# import renaming and forbid struct fields and methods.
383+
# Default: false
384+
expand-expressions: true
369385

370386
funlen:
371387
# Checks the number of lines in a function.

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ require (
149149
github.com/mattn/go-isatty v0.0.17 // indirect
150150
github.com/mattn/go-runewidth v0.0.9 // indirect
151151
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
152-
github.com/mitchellh/mapstructure v1.5.0 // indirect
152+
github.com/mitchellh/mapstructure v1.5.0
153153
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
154154
github.com/olekukonko/tablewriter v0.0.5 // indirect
155155
github.com/pelletier/go-toml v1.9.5 // indirect
@@ -186,6 +186,6 @@ require (
186186
golang.org/x/text v0.6.0 // indirect
187187
google.golang.org/protobuf v1.28.0 // indirect
188188
gopkg.in/ini.v1 v1.67.0 // indirect
189-
gopkg.in/yaml.v2 v2.4.0 // indirect
189+
gopkg.in/yaml.v2 v2.4.0
190190
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
191191
)

pkg/config/linters_settings.go

+40-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package config
22

33
import (
4+
"encoding"
45
"runtime"
56

67
"github.com/pkg/errors"
8+
"gopkg.in/yaml.v2"
79
)
810

911
var defaultLintersSettings = LintersSettings{
@@ -307,10 +309,46 @@ type ExhaustructSettings struct {
307309
}
308310

309311
type ForbidigoSettings struct {
310-
Forbid []string `mapstructure:"forbid"`
311-
ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"`
312+
Forbid []ForbidigoPattern `mapstructure:"forbid"`
313+
ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"`
314+
ExpandExpressions bool `mapstructure:"expand-expressions"`
315+
}
316+
317+
// ForbidigoPattern corresponds to forbidigo.pattern and adds
318+
// mapstructure support. The YAML field names must match what
319+
// forbidigo expects.
320+
type ForbidigoPattern struct {
321+
// patternString gets populated when the config contains a string as
322+
// entry in ForbidigoSettings.Forbid[] because ForbidigoPattern
323+
// implements encoding.TextUnmarshaler and the reader uses the
324+
// mapstructure.TextUnmarshallerHookFunc as decoder hook.
325+
//
326+
// If the entry is a map, then the other fields are set as usual
327+
// by mapstructure.
328+
patternString string
329+
330+
Pattern string `yaml:"p" mapstructure:"p"`
331+
Package string `yaml:"pkg,omitempty" mapstructure:"pkg,omitempty"`
332+
Msg string `yaml:"msg,omitempty" mapstructure:"msg,omitempty"`
333+
}
334+
335+
func (p *ForbidigoPattern) UnmarshalText(text []byte) error {
336+
// Validation happens when instantiating forbidigo.
337+
p.patternString = string(text)
338+
return nil
312339
}
313340

341+
// String is intentionally not called MarshalText because that led to infinite
342+
// recursion when yaml.Marshal called MarshalText.
343+
func (p *ForbidigoPattern) String() ([]byte, error) {
344+
if p.patternString != "" {
345+
return []byte(p.patternString), nil
346+
}
347+
return yaml.Marshal(p)
348+
}
349+
350+
var _ encoding.TextUnmarshaler = &ForbidigoPattern{}
351+
314352
type FunlenSettings struct {
315353
Lines int
316354
Statements int

pkg/config/reader.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/mitchellh/go-homedir"
11+
"github.com/mitchellh/mapstructure"
1112
"github.com/spf13/viper"
1213

1314
"github.com/golangci/golangci-lint/pkg/exitcodes"
@@ -83,7 +84,14 @@ func (r *FileReader) parseConfig() error {
8384
}
8485
r.cfg.cfgDir = usedConfigDir
8586

86-
if err := viper.Unmarshal(r.cfg); err != nil {
87+
if err := viper.Unmarshal(r.cfg, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
88+
// Default hooks (https://github.com/spf13/viper/blob/518241257478c557633ab36e474dfcaeb9a3c623/viper.go#L135-L138).
89+
mapstructure.StringToTimeDurationHookFunc(),
90+
mapstructure.StringToSliceHookFunc(","),
91+
92+
// Needed for forbidigo.
93+
mapstructure.TextUnmarshallerHookFunc(),
94+
))); err != nil {
8795
return fmt.Errorf("can't unmarshal config by viper: %s", err)
8896
}
8997

pkg/golinters/forbidigo.go

+26-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/golangci/golangci-lint/pkg/config"
1111
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
1212
"github.com/golangci/golangci-lint/pkg/lint/linter"
13+
"github.com/golangci/golangci-lint/pkg/logutils"
1314
"github.com/golangci/golangci-lint/pkg/result"
1415
)
1516

@@ -40,14 +41,20 @@ func NewForbidigo(settings *config.ForbidigoSettings) *goanalysis.Linter {
4041
},
4142
}
4243

44+
loadMode := goanalysis.LoadModeSyntax
45+
if settings != nil && settings.ExpandExpressions {
46+
// Need more information for the analysis.
47+
loadMode = goanalysis.LoadModeTypesInfo
48+
}
49+
4350
return goanalysis.NewLinter(
4451
forbidigoName,
4552
"Forbids identifiers",
4653
[]*analysis.Analyzer{analyzer},
4754
nil,
4855
).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
4956
return resIssues
50-
}).WithLoadMode(goanalysis.LoadModeSyntax)
57+
}).WithLoadMode(loadMode)
5158
}
5259

5360
func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) ([]goanalysis.Issue, error) {
@@ -57,14 +64,30 @@ func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) ([]go
5764
forbidigo.OptionIgnorePermitDirectives(true),
5865
}
5966

60-
forbid, err := forbidigo.NewLinter(settings.Forbid, options...)
67+
// Convert patterns back to strings because that is what NewLinter
68+
// accepts.
69+
var patterns []string
70+
for _, pattern := range settings.Forbid {
71+
buffer, err := pattern.String()
72+
if err != nil {
73+
return nil, err
74+
}
75+
patterns = append(patterns, string(buffer))
76+
}
77+
78+
forbid, err := forbidigo.NewLinter(patterns, options...)
6179
if err != nil {
6280
return nil, errors.Wrapf(err, "failed to create linter %q", forbidigoName)
6381
}
6482

6583
var issues []goanalysis.Issue
6684
for _, file := range pass.Files {
67-
hints, err := forbid.RunWithConfig(forbidigo.RunConfig{Fset: pass.Fset}, file)
85+
config := forbidigo.RunConfig{
86+
Fset: pass.Fset,
87+
TypesInfo: pass.TypesInfo,
88+
DebugLog: logutils.Debug(logutils.DebugKeyForbidigo),
89+
}
90+
hints, err := forbid.RunWithConfig(config, file)
6891
if err != nil {
6992
return nil, errors.Wrapf(err, "forbidigo linter failed on file %q", file.Name.String())
7093
}

pkg/lint/lintersdb/manager.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,17 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
414414
WithLoadForGoAnalysis().
415415
WithURL("https://github.com/kyoh86/exportloopref"),
416416

417-
linter.NewConfig(golinters.NewForbidigo(forbidigoCfg)).
418-
WithSince("v1.34.0").
419-
WithPresets(linter.PresetStyle).
420-
WithURL("https://github.com/ashanbrown/forbidigo"),
417+
func() *linter.Config {
418+
l := linter.NewConfig(golinters.NewForbidigo(forbidigoCfg)).
419+
WithSince("v1.34.0").
420+
WithPresets(linter.PresetStyle).
421+
WithURL("https://github.com/ashanbrown/forbidigo")
422+
if forbidigoCfg != nil && forbidigoCfg.ExpandExpressions {
423+
// Need more information for the analysis.
424+
l = l.WithLoadForGoAnalysis()
425+
}
426+
return l
427+
}(),
421428

422429
linter.NewConfig(golinters.NewForceTypeAssert()).
423430
WithSince("v1.38.0").

pkg/logutils/logutils.go

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
DebugKeyExcludeRules = "exclude_rules"
2323
DebugKeyExec = "exec"
2424
DebugKeyFilenameUnadjuster = "filename_unadjuster"
25+
DebugKeyForbidigo = "forbidigo"
2526
DebugKeyGoEnv = "goenv"
2627
DebugKeyLinter = "linter"
2728
DebugKeyLintersContext = "linters_context"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
linters-settings:
2+
forbidigo:
3+
expand-expressions: true
4+
forbid:
5+
- p: fmt\.Print.*
6+
pkg: ^fmt$
7+
- p: time.Sleep
8+
msg: no sleeping!
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//golangcitest:args -Eforbidigo
2+
//golangcitest:config_path testdata/configs/forbidigo_struct.yml
3+
package testdata
4+
5+
import (
6+
fmt2 "fmt"
7+
"time"
8+
)
9+
10+
func Forbidigo() {
11+
fmt2.Printf("too noisy!!!") // want "use of `fmt2\\.Printf` forbidden by pattern `fmt\\\\.Print\\.\\*`"
12+
time.Sleep(time.Nanosecond) // want "no sleeping!"
13+
}

0 commit comments

Comments
 (0)