diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index 6245b6245ba0..13c0b5cf1344 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -631,7 +631,14 @@ linters: ignore-calls: false # Exclude strings matching the given regular expression. # Default: "" - ignore-strings: 'foo.+' + ignore-string-values: + - 'foo.+' + # Detects constants with identical values. + # Default: false + find-duplicates: true + # Evaluates of constant expressions like Prefix + "suffix". + # Default: false + eval-const-expressions: true gocritic: # Disable all checks. diff --git a/go.mod b/go.mod index 142079fc123d..5b321cef38df 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/gostaticanalysis/forcetypeassert v0.2.0 github.com/gostaticanalysis/nilerr v0.1.1 github.com/hashicorp/go-version v1.7.0 - github.com/jgautheron/goconst v1.7.1 + github.com/jgautheron/goconst v1.8.0 github.com/jingyugao/rowserrcheck v1.1.1 github.com/jjti/go-spancheck v0.6.4 github.com/julz/importas v0.2.0 diff --git a/go.sum b/go.sum index 5cfb4c9ad89d..6dbb3f00c85f 100644 --- a/go.sum +++ b/go.sum @@ -331,8 +331,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= -github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.8.0 h1:6XbYfLvGzSu8Fx9x1psDTsM61ecm2txB5CkGGLcfYG4= +github.com/jgautheron/goconst v1.8.0/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 814c7cfe9c8e..8149984411dd 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -1487,9 +1487,12 @@ "type": "boolean", "default": true }, - "ignore-strings": { + "ignore-string-values": { "description": "Exclude strings matching the given regular expression", - "type": "string" + "type": "array", + "items": { + "type": "string" + } }, "numbers": { "description": "Search also for duplicated numbers.", @@ -1505,6 +1508,16 @@ "description": "Maximum value, only works with `numbers`", "type": "integer", "default": 3 + }, + "find-duplicates": { + "description": "Detects constants with identical values", + "type": "boolean", + "default": false + }, + "eval-const-expressions": { + "description": "Evaluates of constant expressions like Prefix + \"suffix\"", + "type": "boolean", + "default": false } } }, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 046645e40f98..16269033c7e9 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -451,14 +451,19 @@ type GocognitSettings struct { } type GoConstSettings struct { - IgnoreStrings string `mapstructure:"ignore-strings"` - MatchWithConstants bool `mapstructure:"match-constant"` - MinStringLen int `mapstructure:"min-len"` - MinOccurrencesCount int `mapstructure:"min-occurrences"` - ParseNumbers bool `mapstructure:"numbers"` - NumberMin int `mapstructure:"min"` - NumberMax int `mapstructure:"max"` - IgnoreCalls bool `mapstructure:"ignore-calls"` + IgnoreStringValues []string `mapstructure:"ignore-string-values"` + MatchWithConstants bool `mapstructure:"match-constant"` + MinStringLen int `mapstructure:"min-len"` + MinOccurrencesCount int `mapstructure:"min-occurrences"` + ParseNumbers bool `mapstructure:"numbers"` + NumberMin int `mapstructure:"min"` + NumberMax int `mapstructure:"max"` + IgnoreCalls bool `mapstructure:"ignore-calls"` + FindDuplicates bool `mapstructure:"find-duplicates"` + EvalConstExpressions bool `mapstructure:"eval-const-expressions"` + + // Deprecated: use IgnoreStringValues instead. + IgnoreStrings string `mapstructure:"ignore-strings"` } type GoCriticSettings struct { diff --git a/pkg/config/loader.go b/pkg/config/loader.go index ffa02d6bcdb5..19a17e7d71db 100644 --- a/pkg/config/loader.go +++ b/pkg/config/loader.go @@ -190,8 +190,15 @@ func (l *Loader) handleDeprecation() error { return nil } -func (*Loader) handleLinterOptionDeprecations() { - // The function is empty but deprecations will happen in the future. +func (l *Loader) handleLinterOptionDeprecations() { + // Deprecated since v2.1.0. + if l.cfg.Linters.Settings.Goconst.IgnoreStrings != "" { + l.log.Warnf("The configuration option `linters.settings.goconst.ignore-strings` is deprecated, " + + "please use `linters.settings.goconst.ignore-string-values`.") + + l.cfg.Linters.Settings.Goconst.IgnoreStringValues = append(l.cfg.Linters.Settings.Goconst.IgnoreStringValues, + l.cfg.Linters.Settings.Goconst.IgnoreStrings) + } } func (l *Loader) handleEnableOnlyOption() error { diff --git a/pkg/golinters/goconst/goconst.go b/pkg/golinters/goconst/goconst.go index 26196f6929d6..f3af64625243 100644 --- a/pkg/golinters/goconst/goconst.go +++ b/pkg/golinters/goconst/goconst.go @@ -48,19 +48,21 @@ func New(settings *config.GoConstSettings) *goanalysis.Linter { nil, ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + }).WithLoadMode(goanalysis.LoadModeTypesInfo) } func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanalysis.Issue, error) { cfg := goconstAPI.Config{ - IgnoreStrings: settings.IgnoreStrings, - MatchWithConstants: settings.MatchWithConstants, - MinStringLength: settings.MinStringLen, - MinOccurrences: settings.MinOccurrencesCount, - ParseNumbers: settings.ParseNumbers, - NumberMin: settings.NumberMin, - NumberMax: settings.NumberMax, - ExcludeTypes: map[goconstAPI.Type]bool{}, + IgnoreStrings: settings.IgnoreStringValues, + MatchWithConstants: settings.MatchWithConstants, + MinStringLength: settings.MinStringLen, + MinOccurrences: settings.MinOccurrencesCount, + ParseNumbers: settings.ParseNumbers, + NumberMin: settings.NumberMin, + NumberMax: settings.NumberMax, + ExcludeTypes: map[goconstAPI.Type]bool{}, + FindDuplicates: settings.FindDuplicates, + EvalConstExpressions: settings.EvalConstExpressions, // Should be managed with `linters.exclusions.rules`. IgnoreTests: false, @@ -70,7 +72,7 @@ func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanal cfg.ExcludeTypes[goconstAPI.Call] = true } - lintIssues, err := goconstAPI.Run(pass.Files, pass.Fset, &cfg) + lintIssues, err := goconstAPI.Run(pass.Files, pass.Fset, pass.TypesInfo, &cfg) if err != nil { return nil, err } @@ -80,17 +82,32 @@ func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]goanal } res := make([]goanalysis.Issue, 0, len(lintIssues)) - for _, i := range lintIssues { - text := fmt.Sprintf("string %s has %d occurrences", internal.FormatCode(i.Str, nil), i.OccurrencesCount) + for i := range lintIssues { + issue := &lintIssues[i] - if i.MatchingConst == "" { - text += ", make it a constant" - } else { - text += fmt.Sprintf(", but such constant %s already exists", internal.FormatCode(i.MatchingConst, nil)) + var text string + + switch { + case issue.OccurrencesCount > 0: + text = fmt.Sprintf("string %s has %d occurrences", internal.FormatCode(issue.Str, nil), issue.OccurrencesCount) + + if issue.MatchingConst == "" { + text += ", make it a constant" + } else { + text += fmt.Sprintf(", but such constant %s already exists", internal.FormatCode(issue.MatchingConst, nil)) + } + + case issue.DuplicateConst != "": + text = fmt.Sprintf("This constant is a duplicate of %s at %s", + internal.FormatCode(issue.DuplicateConst, nil), + issue.DuplicatePos.String()) + + default: + continue } res = append(res, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, + Pos: issue.Pos, Text: text, FromLinter: linterName, }, pass)) diff --git a/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.go b/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.go new file mode 100644 index 000000000000..24d43af14df6 --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.go @@ -0,0 +1,25 @@ +//golangcitest:args -Egoconst +//golangcitest:config_path testdata/goconst_eval_and_find_duplicates.yml +package testdata + +import "fmt" + +const ( + envPrefix = "FOO_" + EnvUser = envPrefix + "USER" + EnvPassword = envPrefix + "PASSWORD" +) + +const EnvUserFull = "FOO_USER" // want "This constant is a duplicate of `EnvUser` at goconst_eval_and_find_duplicates.go:9:16" + +const KiB = 1 << 10 + +func _() { + fmt.Println(envPrefix, EnvUser, EnvPassword, EnvUserFull) + + const kilobytes = 1024 // want "This constant is a duplicate of `KiB` at goconst_eval_and_find_duplicates.go:15:13" + fmt.Println(kilobytes) + + kib := 1024 + fmt.Println(kib) +} diff --git a/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.yml b/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.yml new file mode 100644 index 000000000000..445d6b77263d --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_eval_and_find_duplicates.yml @@ -0,0 +1,8 @@ +version: "2" + +linters: + settings: + goconst: + find-duplicates: true + eval-const-expressions: true + numbers: true diff --git a/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.go b/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.go new file mode 100644 index 000000000000..4cc272945b43 --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egoconst +//golangcitest:config_path testdata/goconst_eval_const_expressions.yml +package testdata + +const ( + prefix = "example.com/" + API = prefix + "api" + Web = prefix + "web" +) + +const Full = "example.com/api" + +func _() { + a0 := "example.com/api" // want "string `example.com/api` has 3 occurrences, but such constant `API` already exists" + a1 := "example.com/api" + a2 := "example.com/api" + + _ = a0 + _ = a1 + _ = a2 + + b0 := "example.com/web" // want "string `example.com/web` has 3 occurrences, but such constant `Web` already exists" + b1 := "example.com/web" + b2 := "example.com/web" + + _ = b0 + _ = b1 + _ = b2 +} diff --git a/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.yml b/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.yml new file mode 100644 index 000000000000..7c2303aef7d8 --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_eval_const_expressions.yml @@ -0,0 +1,6 @@ +version: "2" + +linters: + settings: + goconst: + eval-const-expressions: true diff --git a/pkg/golinters/goconst/testdata/goconst_find_duplicates.go b/pkg/golinters/goconst/testdata/goconst_find_duplicates.go new file mode 100644 index 000000000000..d26d871daf37 --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_find_duplicates.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egoconst +//golangcitest:config_path testdata/goconst_find_duplicates.yml +package testdata + +const SingleConst = "single constant" + +const ( + GroupedConst1 = "grouped constant" + GroupedConst2 = "another grouped" +) + +const ( + GroupedDuplicateConst1 = "grouped duplicate value" + GroupedDuplicateConst2 = "grouped duplicate value" // want "This constant is a duplicate of `GroupedDuplicateConst1` at goconst_find_duplicates.go:13:2" +) + +const DuplicateConst1 = "duplicate value" + +const DuplicateConst2 = "duplicate value" // want "This constant is a duplicate of `DuplicateConst1` at goconst_find_duplicates.go:17:7" + +const ( + SpecialDuplicateConst1 = "special\nvalue\twith\rchars" + SpecialDuplicateConst2 = "special\nvalue\twith\rchars" // want "This constant is a duplicate of `SpecialDuplicateConst1` at goconst_find_duplicates.go:22:2" +) + +func _() { + const DuplicateScopedConst1 = "duplicate scoped value" + const DuplicateScopedConst2 = "duplicate scoped value" // want "This constant is a duplicate of `DuplicateScopedConst1` at goconst_find_duplicates.go:27:8" +} diff --git a/pkg/golinters/goconst/testdata/goconst_find_duplicates.yml b/pkg/golinters/goconst/testdata/goconst_find_duplicates.yml new file mode 100644 index 000000000000..8a64450696e7 --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_find_duplicates.yml @@ -0,0 +1,6 @@ +version: "2" + +linters: + settings: + goconst: + find-duplicates: true diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index c246250dad16..a05e0bbd7f4c 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -300,6 +300,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(goconst.New(&cfg.Linters.Settings.Goconst)). WithSince("v1.0.0"). + WithLoadForGoAnalysis(). WithURL("https://github.com/jgautheron/goconst"), linter.NewConfig(gocritic.New(&cfg.Linters.Settings.Gocritic, placeholderReplacer)).