Skip to content

Commit 2c01ea7

Browse files
gocritic: add support for variable substitution in ruleguard path settings (#2308)
* Add variable for ruleguard config directory * Add variable for ruleguard config directory * Add variable for ruleguard config directory * Add variable for ruleguard config directory * Add unit tests * Add unit tests for ruleguard * Add unit tests for ruleguard * Add unit tests for ruleguard * Add unit tests for ruleguard, fix package name
1 parent f9f6486 commit 2c01ea7

16 files changed

+98
-9
lines changed

.golangci.example.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ linters-settings:
196196
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
197197
# By default list of stable checks is used.
198198
enabled-checks:
199-
- rangeValCopy
199+
- nestingReduce
200+
- unnamedresult
201+
- ruleguard
202+
- truncateCmp
200203

201204
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
202205
disabled-checks:

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ require (
6363
github.com/pkg/errors v0.9.1
6464
github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349
6565
github.com/prometheus/procfs v0.6.0 // indirect
66+
github.com/quasilyte/go-ruleguard/dsl v0.3.10
6667
github.com/ryancurrah/gomodguard v1.2.3
6768
github.com/ryanrolds/sqlclosecheck v0.3.0
6869
github.com/sanposhiho/wastedassign/v2 v2.0.6

go.sum

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/commands/executor.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Executor struct {
5555
flock *flock.Flock
5656
}
5757

58+
// NewExecutor creates and initializes a new command executor.
5859
func NewExecutor(version, commit, date string) *Executor {
5960
startedAt := time.Now()
6061
e := &Executor{

pkg/commands/linters.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func (e *Executor) initLinters() {
2020
e.initRunConfiguration(e.lintersCmd)
2121
}
2222

23+
// executeLinters runs the 'linters' CLI command, which displays the supported linters.
2324
func (e *Executor) executeLinters(_ *cobra.Command, args []string) {
2425
if len(args) != 0 {
2526
e.log.Fatalf("Usage: golangci-lint linters")

pkg/commands/run.go

+2
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ func fixSlicesFlags(fs *pflag.FlagSet) {
323323
})
324324
}
325325

326+
// runAnalysis executes the linters that have been enabled in the configuration.
326327
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
327328
e.cfg.Run.Args = args
328329

@@ -444,6 +445,7 @@ func (e *Executor) createPrinter() (printers.Printer, error) {
444445
return p, nil
445446
}
446447

448+
// executeRun executes the 'run' CLI command, which runs the linters.
447449
func (e *Executor) executeRun(_ *cobra.Command, args []string) {
448450
needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
449451
trackResourcesEndCh := make(chan struct{})

pkg/config/config.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package config
22

33
// Config encapsulates the config data specified in the golangci yaml config file.
44
type Config struct {
5-
Run Run
5+
cfgDir string // The directory containing the golangci config file.
6+
Run Run
67

78
Output Output
89

@@ -16,6 +17,11 @@ type Config struct {
1617
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
1718
}
1819

20+
// getConfigDir returns the directory that contains golangci config file.
21+
func (c *Config) GetConfigDir() string {
22+
return c.cfgDir
23+
}
24+
1925
func NewDefault() *Config {
2026
return &Config{
2127
LintersSettings: defaultLintersSettings,

pkg/config/reader.go

+5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ func (r *FileReader) parseConfig() error {
7171
r.log.Warnf("Can't pretty print config file path: %s", err)
7272
}
7373
r.log.Infof("Used config file %s", usedConfigFile)
74+
usedConfigDir := filepath.Dir(usedConfigFile)
75+
if usedConfigDir, err = filepath.Abs(usedConfigDir); err != nil {
76+
return fmt.Errorf("can't get config directory")
77+
}
78+
r.cfg.cfgDir = usedConfigDir
7479

7580
if err := viper.Unmarshal(r.cfg); err != nil {
7681
return fmt.Errorf("can't unmarshal config by viper: %s", err)

pkg/config/run.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Run struct {
1212
Concurrency int
1313
PrintResourcesUsage bool `mapstructure:"print-resources-usage"`
1414

15-
Config string
15+
Config string // The path to the golangci config file, as specified with the --config argument.
1616
NoConfig bool
1717

1818
Args []string

pkg/golinters/gocritic.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter
7878
return ret
7979
}
8080

81-
func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error {
81+
func configureCheckerInfo(
82+
lintCtx *linter.Context,
83+
info *gocriticlinter.CheckerInfo,
84+
allParams map[string]config.GocriticCheckSettings) error {
8285
params := allParams[strings.ToLower(info.Name)]
8386
if params == nil { // no config for this checker
8487
return nil
@@ -88,7 +91,7 @@ func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string
8891
for k, p := range params {
8992
v, ok := infoParams[k]
9093
if ok {
91-
v.Value = normalizeCheckerParamsValue(p)
94+
v.Value = normalizeCheckerParamsValue(lintCtx, p)
9295
continue
9396
}
9497

@@ -117,15 +120,16 @@ func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string
117120
// then we have to convert value types into the expected value types.
118121
// Maybe in the future, this kind of conversion will be done in go-critic itself.
119122
//nolint:exhaustive // only 3 types (int, bool, and string) are supported by CheckerParam.Value
120-
func normalizeCheckerParamsValue(p interface{}) interface{} {
123+
func normalizeCheckerParamsValue(lintCtx *linter.Context, p interface{}) interface{} {
121124
rv := reflect.ValueOf(p)
122125
switch rv.Type().Kind() {
123126
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
124127
return int(rv.Int())
125128
case reflect.Bool:
126129
return rv.Bool()
127130
case reflect.String:
128-
return rv.String()
131+
// Perform variable substitution.
132+
return strings.ReplaceAll(rv.String(), "${configDir}", lintCtx.Cfg.GetConfigDir())
129133
default:
130134
return p
131135
}
@@ -141,7 +145,7 @@ func buildEnabledCheckers(lintCtx *linter.Context, linterCtx *gocriticlinter.Con
141145
continue
142146
}
143147

144-
if err := configureCheckerInfo(info, allParams); err != nil {
148+
if err := configureCheckerInfo(lintCtx, info, allParams); err != nil {
145149
return nil, err
146150
}
147151

test/linters_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func runGoErrchk(c *exec.Cmd, defaultExpectedLinter string, files []string, t *t
2323
if err != nil {
2424
var exitErr *exec.ExitError
2525
require.ErrorAs(t, err, &exitErr)
26-
require.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
26+
require.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode(), "Unexpected exit code: %s", string(output))
2727
}
2828

2929
fullshort := make([]string, 0, len(files)*2)

test/ruleguard/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This directory contains ruleguard files that are used in functional tests.

test/ruleguard/dup.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// go:build ruleguard
2+
package ruleguard
3+
4+
import "github.com/quasilyte/go-ruleguard/dsl"
5+
6+
// Suppose that we want to report the duplicated left and right operands of binary operations.
7+
//
8+
// But if the operand has some side effects, this rule can cause false positives:
9+
// `f() && f()` can make sense (although it's not the best piece of code).
10+
//
11+
// This is where *filters* come to the rescue.
12+
func DupSubExpr(m dsl.Matcher) {
13+
// All filters are written as a Where() argument.
14+
// In our case, we need to assert that $x is "pure".
15+
// It can be achieved by checking the m["x"] member Pure field.
16+
m.Match(`$x || $x`,
17+
`$x && $x`,
18+
`$x | $x`,
19+
`$x & $x`).
20+
Where(m["x"].Pure).
21+
Report(`suspicious identical LHS and RHS`)
22+
}

test/ruleguard/strings_simplify.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// go:build ruleguard
2+
package ruleguard
3+
4+
import "github.com/quasilyte/go-ruleguard/dsl"
5+
6+
func StringsSimplify(m dsl.Matcher) {
7+
// Some issues have simple fixes that can be expressed as
8+
// a replacement pattern. Rules can use Suggest() function
9+
// to add a quickfix action for such issues.
10+
m.Match(`strings.Replace($s, $old, $new, -1)`).
11+
Report(`this Replace call can be simplified`).
12+
Suggest(`strings.ReplaceAll($s, $old, $new)`)
13+
14+
// Suggest() can be used without Report().
15+
// It'll print the suggested template to the user.
16+
m.Match(`strings.Count($s1, $s2) == 0`).
17+
Suggest(`!strings.Contains($s1, $s2)`)
18+
}

test/testdata/configs/gocritic.yml

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ linters-settings:
33
enabled-checks:
44
- rangeValCopy
55
- flagDeref
6+
- ruleguard
67
settings:
78
rangevalcopy:
89
sizethreshold: 2
10+
ruleguard:
11+
debug: dupSubExpr
12+
failOn: dsl,import
13+
# comma-separated paths to ruleguard files.
14+
# The ${configDir} is substituted by the directory containing the golangci-lint config file.
15+
# Note about the directory structure for functional tests:
16+
# The ruleguard files used in functional tests cannot be under the 'testdata' directory.
17+
# This is because they import the 'github.com/quasilyte/go-ruleguard/dsl' package,
18+
# which needs to be added to go.mod. The testdata directory is ignored by go mod.
19+
rules: '${configDir}/../../ruleguard/strings_simplify.go,${configDir}/../../ruleguard/dup.go'

test/testdata/gocritic.go

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package testdata
55
import (
66
"flag"
77
"log"
8+
"strings"
89
)
910

1011
var _ = *flag.Bool("global1", false, "") // ERROR `flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar`
@@ -29,3 +30,15 @@ func gocriticRangeValCopySize2(ss []size2) {
2930
log.Print(s)
3031
}
3132
}
33+
34+
func gocriticStringSimplify() {
35+
s := "Most of the time, travellers worry about their luggage."
36+
s = strings.Replace(s, ",", "", -1) // ERROR "ruleguard: this Replace call can be simplified.*"
37+
log.Print(s)
38+
}
39+
40+
func gocriticDup(x bool) {
41+
if x && x { // ERROR "ruleguard: suspicious identical LHS and RHS.*"
42+
log.Print("x is true")
43+
}
44+
}

0 commit comments

Comments
 (0)