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
This commit is contained in:
parent
f9f6486e15
commit
2c01ea7ff2
@ -196,7 +196,10 @@ linters-settings:
|
|||||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||||
# By default list of stable checks is used.
|
# By default list of stable checks is used.
|
||||||
enabled-checks:
|
enabled-checks:
|
||||||
- rangeValCopy
|
- nestingReduce
|
||||||
|
- unnamedresult
|
||||||
|
- ruleguard
|
||||||
|
- truncateCmp
|
||||||
|
|
||||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
|
1
go.mod
1
go.mod
@ -63,6 +63,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349
|
github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349
|
||||||
github.com/prometheus/procfs v0.6.0 // indirect
|
github.com/prometheus/procfs v0.6.0 // indirect
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.10
|
||||||
github.com/ryancurrah/gomodguard v1.2.3
|
github.com/ryancurrah/gomodguard v1.2.3
|
||||||
github.com/ryanrolds/sqlclosecheck v0.3.0
|
github.com/ryanrolds/sqlclosecheck v0.3.0
|
||||||
github.com/sanposhiho/wastedassign/v2 v2.0.6
|
github.com/sanposhiho/wastedassign/v2 v2.0.6
|
||||||
|
1
go.sum
generated
1
go.sum
generated
@ -622,6 +622,7 @@ github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1
|
|||||||
github.com/quasilyte/go-ruleguard v0.3.13 h1:O1G41cq1jUr3cJmqp7vOUT0SokqjzmS9aESWJuIDRaY=
|
github.com/quasilyte/go-ruleguard v0.3.13 h1:O1G41cq1jUr3cJmqp7vOUT0SokqjzmS9aESWJuIDRaY=
|
||||||
github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ=
|
github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ=
|
||||||
github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.10 h1:4tVlVVcBT+nNWoF+t/zrAMO13sHAqYotX1K12Gc8f8A=
|
||||||
github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||||
github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
|
github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
|
||||||
github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
|
github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
|
||||||
|
@ -55,6 +55,7 @@ type Executor struct {
|
|||||||
flock *flock.Flock
|
flock *flock.Flock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewExecutor creates and initializes a new command executor.
|
||||||
func NewExecutor(version, commit, date string) *Executor {
|
func NewExecutor(version, commit, date string) *Executor {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
e := &Executor{
|
e := &Executor{
|
||||||
|
@ -20,6 +20,7 @@ func (e *Executor) initLinters() {
|
|||||||
e.initRunConfiguration(e.lintersCmd)
|
e.initRunConfiguration(e.lintersCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executeLinters runs the 'linters' CLI command, which displays the supported linters.
|
||||||
func (e *Executor) executeLinters(_ *cobra.Command, args []string) {
|
func (e *Executor) executeLinters(_ *cobra.Command, args []string) {
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
e.log.Fatalf("Usage: golangci-lint linters")
|
e.log.Fatalf("Usage: golangci-lint linters")
|
||||||
|
@ -323,6 +323,7 @@ func fixSlicesFlags(fs *pflag.FlagSet) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runAnalysis executes the linters that have been enabled in the configuration.
|
||||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
||||||
e.cfg.Run.Args = args
|
e.cfg.Run.Args = args
|
||||||
|
|
||||||
@ -444,6 +445,7 @@ func (e *Executor) createPrinter() (printers.Printer, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executeRun executes the 'run' CLI command, which runs the linters.
|
||||||
func (e *Executor) executeRun(_ *cobra.Command, args []string) {
|
func (e *Executor) executeRun(_ *cobra.Command, args []string) {
|
||||||
needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
|
needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
|
||||||
trackResourcesEndCh := make(chan struct{})
|
trackResourcesEndCh := make(chan struct{})
|
||||||
|
@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
// Config encapsulates the config data specified in the golangci yaml config file.
|
// Config encapsulates the config data specified in the golangci yaml config file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
cfgDir string // The directory containing the golangci config file.
|
||||||
Run Run
|
Run Run
|
||||||
|
|
||||||
Output Output
|
Output Output
|
||||||
@ -16,6 +17,11 @@ type Config struct {
|
|||||||
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
|
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getConfigDir returns the directory that contains golangci config file.
|
||||||
|
func (c *Config) GetConfigDir() string {
|
||||||
|
return c.cfgDir
|
||||||
|
}
|
||||||
|
|
||||||
func NewDefault() *Config {
|
func NewDefault() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
LintersSettings: defaultLintersSettings,
|
LintersSettings: defaultLintersSettings,
|
||||||
|
@ -71,6 +71,11 @@ func (r *FileReader) parseConfig() error {
|
|||||||
r.log.Warnf("Can't pretty print config file path: %s", err)
|
r.log.Warnf("Can't pretty print config file path: %s", err)
|
||||||
}
|
}
|
||||||
r.log.Infof("Used config file %s", usedConfigFile)
|
r.log.Infof("Used config file %s", usedConfigFile)
|
||||||
|
usedConfigDir := filepath.Dir(usedConfigFile)
|
||||||
|
if usedConfigDir, err = filepath.Abs(usedConfigDir); err != nil {
|
||||||
|
return fmt.Errorf("can't get config directory")
|
||||||
|
}
|
||||||
|
r.cfg.cfgDir = usedConfigDir
|
||||||
|
|
||||||
if err := viper.Unmarshal(r.cfg); err != nil {
|
if err := viper.Unmarshal(r.cfg); err != nil {
|
||||||
return fmt.Errorf("can't unmarshal config by viper: %s", err)
|
return fmt.Errorf("can't unmarshal config by viper: %s", err)
|
||||||
|
@ -12,7 +12,7 @@ type Run struct {
|
|||||||
Concurrency int
|
Concurrency int
|
||||||
PrintResourcesUsage bool `mapstructure:"print-resources-usage"`
|
PrintResourcesUsage bool `mapstructure:"print-resources-usage"`
|
||||||
|
|
||||||
Config string
|
Config string // The path to the golangci config file, as specified with the --config argument.
|
||||||
NoConfig bool
|
NoConfig bool
|
||||||
|
|
||||||
Args []string
|
Args []string
|
||||||
|
@ -78,7 +78,10 @@ func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string]config.GocriticCheckSettings) error {
|
func configureCheckerInfo(
|
||||||
|
lintCtx *linter.Context,
|
||||||
|
info *gocriticlinter.CheckerInfo,
|
||||||
|
allParams map[string]config.GocriticCheckSettings) error {
|
||||||
params := allParams[strings.ToLower(info.Name)]
|
params := allParams[strings.ToLower(info.Name)]
|
||||||
if params == nil { // no config for this checker
|
if params == nil { // no config for this checker
|
||||||
return nil
|
return nil
|
||||||
@ -88,7 +91,7 @@ func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string
|
|||||||
for k, p := range params {
|
for k, p := range params {
|
||||||
v, ok := infoParams[k]
|
v, ok := infoParams[k]
|
||||||
if ok {
|
if ok {
|
||||||
v.Value = normalizeCheckerParamsValue(p)
|
v.Value = normalizeCheckerParamsValue(lintCtx, p)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +120,7 @@ func configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string
|
|||||||
// then we have to convert value types into the expected value types.
|
// then we have to convert value types into the expected value types.
|
||||||
// Maybe in the future, this kind of conversion will be done in go-critic itself.
|
// Maybe in the future, this kind of conversion will be done in go-critic itself.
|
||||||
//nolint:exhaustive // only 3 types (int, bool, and string) are supported by CheckerParam.Value
|
//nolint:exhaustive // only 3 types (int, bool, and string) are supported by CheckerParam.Value
|
||||||
func normalizeCheckerParamsValue(p interface{}) interface{} {
|
func normalizeCheckerParamsValue(lintCtx *linter.Context, p interface{}) interface{} {
|
||||||
rv := reflect.ValueOf(p)
|
rv := reflect.ValueOf(p)
|
||||||
switch rv.Type().Kind() {
|
switch rv.Type().Kind() {
|
||||||
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
|
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
|
||||||
@ -125,7 +128,8 @@ func normalizeCheckerParamsValue(p interface{}) interface{} {
|
|||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return rv.Bool()
|
return rv.Bool()
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return rv.String()
|
// Perform variable substitution.
|
||||||
|
return strings.ReplaceAll(rv.String(), "${configDir}", lintCtx.Cfg.GetConfigDir())
|
||||||
default:
|
default:
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@ -141,7 +145,7 @@ func buildEnabledCheckers(lintCtx *linter.Context, linterCtx *gocriticlinter.Con
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := configureCheckerInfo(info, allParams); err != nil {
|
if err := configureCheckerInfo(lintCtx, info, allParams); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ func runGoErrchk(c *exec.Cmd, defaultExpectedLinter string, files []string, t *t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var exitErr *exec.ExitError
|
var exitErr *exec.ExitError
|
||||||
require.ErrorAs(t, err, &exitErr)
|
require.ErrorAs(t, err, &exitErr)
|
||||||
require.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
|
require.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode(), "Unexpected exit code: %s", string(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
fullshort := make([]string, 0, len(files)*2)
|
fullshort := make([]string, 0, len(files)*2)
|
||||||
|
1
test/ruleguard/README.md
Normal file
1
test/ruleguard/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
This directory contains ruleguard files that are used in functional tests.
|
22
test/ruleguard/dup.go
Normal file
22
test/ruleguard/dup.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// go:build ruleguard
|
||||||
|
package ruleguard
|
||||||
|
|
||||||
|
import "github.com/quasilyte/go-ruleguard/dsl"
|
||||||
|
|
||||||
|
// Suppose that we want to report the duplicated left and right operands of binary operations.
|
||||||
|
//
|
||||||
|
// But if the operand has some side effects, this rule can cause false positives:
|
||||||
|
// `f() && f()` can make sense (although it's not the best piece of code).
|
||||||
|
//
|
||||||
|
// This is where *filters* come to the rescue.
|
||||||
|
func DupSubExpr(m dsl.Matcher) {
|
||||||
|
// All filters are written as a Where() argument.
|
||||||
|
// In our case, we need to assert that $x is "pure".
|
||||||
|
// It can be achieved by checking the m["x"] member Pure field.
|
||||||
|
m.Match(`$x || $x`,
|
||||||
|
`$x && $x`,
|
||||||
|
`$x | $x`,
|
||||||
|
`$x & $x`).
|
||||||
|
Where(m["x"].Pure).
|
||||||
|
Report(`suspicious identical LHS and RHS`)
|
||||||
|
}
|
18
test/ruleguard/strings_simplify.go
Normal file
18
test/ruleguard/strings_simplify.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// go:build ruleguard
|
||||||
|
package ruleguard
|
||||||
|
|
||||||
|
import "github.com/quasilyte/go-ruleguard/dsl"
|
||||||
|
|
||||||
|
func StringsSimplify(m dsl.Matcher) {
|
||||||
|
// Some issues have simple fixes that can be expressed as
|
||||||
|
// a replacement pattern. Rules can use Suggest() function
|
||||||
|
// to add a quickfix action for such issues.
|
||||||
|
m.Match(`strings.Replace($s, $old, $new, -1)`).
|
||||||
|
Report(`this Replace call can be simplified`).
|
||||||
|
Suggest(`strings.ReplaceAll($s, $old, $new)`)
|
||||||
|
|
||||||
|
// Suggest() can be used without Report().
|
||||||
|
// It'll print the suggested template to the user.
|
||||||
|
m.Match(`strings.Count($s1, $s2) == 0`).
|
||||||
|
Suggest(`!strings.Contains($s1, $s2)`)
|
||||||
|
}
|
11
test/testdata/configs/gocritic.yml
vendored
11
test/testdata/configs/gocritic.yml
vendored
@ -3,6 +3,17 @@ linters-settings:
|
|||||||
enabled-checks:
|
enabled-checks:
|
||||||
- rangeValCopy
|
- rangeValCopy
|
||||||
- flagDeref
|
- flagDeref
|
||||||
|
- ruleguard
|
||||||
settings:
|
settings:
|
||||||
rangevalcopy:
|
rangevalcopy:
|
||||||
sizethreshold: 2
|
sizethreshold: 2
|
||||||
|
ruleguard:
|
||||||
|
debug: dupSubExpr
|
||||||
|
failOn: dsl,import
|
||||||
|
# comma-separated paths to ruleguard files.
|
||||||
|
# The ${configDir} is substituted by the directory containing the golangci-lint config file.
|
||||||
|
# Note about the directory structure for functional tests:
|
||||||
|
# The ruleguard files used in functional tests cannot be under the 'testdata' directory.
|
||||||
|
# This is because they import the 'github.com/quasilyte/go-ruleguard/dsl' package,
|
||||||
|
# which needs to be added to go.mod. The testdata directory is ignored by go mod.
|
||||||
|
rules: '${configDir}/../../ruleguard/strings_simplify.go,${configDir}/../../ruleguard/dup.go'
|
||||||
|
13
test/testdata/gocritic.go
vendored
13
test/testdata/gocritic.go
vendored
@ -5,6 +5,7 @@ package testdata
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = *flag.Bool("global1", false, "") // ERROR `flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar`
|
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) {
|
|||||||
log.Print(s)
|
log.Print(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gocriticStringSimplify() {
|
||||||
|
s := "Most of the time, travellers worry about their luggage."
|
||||||
|
s = strings.Replace(s, ",", "", -1) // ERROR "ruleguard: this Replace call can be simplified.*"
|
||||||
|
log.Print(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gocriticDup(x bool) {
|
||||||
|
if x && x { // ERROR "ruleguard: suspicious identical LHS and RHS.*"
|
||||||
|
log.Print("x is true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user