From 5a97b5d57a1d97794946d82cde58b349439e4605 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Fri, 19 Nov 2021 21:49:35 +0100 Subject: [PATCH] Implement binary output and merge mode We implement a merge mode, which takes the results of multiple runs and merges them. On an analysis-by-analysis basis we decide whether a diagnostic has to occur in all runs to be merged. In addition, we take into consideration whether a file was part of a run in the first place, to avoid incorrectly dropping diagnostics. We add a new output format called "binary", which writes diagnostics and metadata using gob. This data is the input to the merge mode. Currently, the metadata includes a list of all files that were checked. In the future, we might change to amore finegrained data, such as which lines of code were reachable in the IR. Updates gh-938 --- analysis/lint/lint.go | 10 ++ lintcmd/cmd.go | 332 ++++++++++++++++++++++++++++++------------ lintcmd/lint.go | 55 ++----- simple/doc.go | 174 ++++++++++++++-------- staticcheck/doc.go | 96 ++++++++++++ stylecheck/doc.go | 44 ++++-- 6 files changed, 502 insertions(+), 209 deletions(-) diff --git a/analysis/lint/lint.go b/analysis/lint/lint.go index f5aab1b31..789d44229 100644 --- a/analysis/lint/lint.go +++ b/analysis/lint/lint.go @@ -55,6 +55,13 @@ const ( SeverityHint ) +type MergeStrategy int + +const ( + MergeIfAny MergeStrategy = iota + MergeIfAll +) + type RawDocumentation struct { Title string Text string @@ -64,6 +71,7 @@ type RawDocumentation struct { NonDefault bool Options []string Severity Severity + MergeIf MergeStrategy } type Documentation struct { @@ -79,6 +87,7 @@ type Documentation struct { NonDefault bool Options []string Severity Severity + MergeIf MergeStrategy } func Markdownify(m map[string]*RawDocumentation) map[string]*Documentation { @@ -97,6 +106,7 @@ func Markdownify(m map[string]*RawDocumentation) map[string]*Documentation { NonDefault: v.NonDefault, Options: v.Options, Severity: v.Severity, + MergeIf: v.MergeIf, } } return out diff --git a/lintcmd/cmd.go b/lintcmd/cmd.go index 006cd2571..3e79498fe 100644 --- a/lintcmd/cmd.go +++ b/lintcmd/cmd.go @@ -3,8 +3,10 @@ package lintcmd import ( + "encoding/gob" "flag" "fmt" + "go/token" "log" "os" "reflect" @@ -25,6 +27,11 @@ import ( "golang.org/x/tools/go/buildutil" ) +type binaryOutput struct { + CheckedFiles []string + Problems []problem +} + // Command represents a linter command line tool. type Command struct { name string @@ -42,6 +49,7 @@ type Command struct { formatter string explain string listChecks bool + merge bool debugCpuprofile string debugMemprofile string @@ -217,23 +225,6 @@ func (cmd *Command) Run() { } } - exit := func(code int) { - if cmd.flags.debugCpuprofile != "" { - pprof.StopCPUProfile() - } - if path := cmd.flags.debugMemprofile; path != "" { - f, err := os.Create(path) - if err != nil { - panic(err) - } - runtime.GC() - pprof.WriteHeapProfile(f) - } - if cmd.flags.debugTrace != "" { - trace.Stop() - } - os.Exit(code) - } if path := cmd.flags.debugCpuprofile; path != "" { f, err := os.Create(path) if err != nil { @@ -259,10 +250,11 @@ func (cmd *Command) Run() { } config.DefaultConfig.Checks = defaultChecks - if cmd.flags.debugVersion { + switch { + case cmd.flags.debugVersion: version.Verbose(cmd.version, cmd.machineVersion) - exit(0) - } else if cmd.flags.listChecks { + cmd.exit(0) + case cmd.flags.listChecks: sort.Slice(cs, func(i, j int) bool { return cs[i].Analyzer.Name < cs[j].Analyzer.Name }) @@ -273,58 +265,118 @@ func (cmd *Command) Run() { } fmt.Printf("%s %s\n", c.Analyzer.Name, title) } - exit(0) - } else if cmd.flags.printVersion { + cmd.exit(0) + case cmd.flags.printVersion: version.Print(cmd.version, cmd.machineVersion) - exit(0) - } else if explain := cmd.flags.explain; explain != "" { - if explain := cmd.flags.explain; explain != "" { - check, ok := cmd.analyzers[explain] - if !ok { - fmt.Fprintln(os.Stderr, "Couldn't find check", explain) - exit(1) + cmd.exit(0) + case cmd.flags.explain != "": + explain := cmd.flags.explain + check, ok := cmd.analyzers[explain] + if !ok { + fmt.Fprintln(os.Stderr, "Couldn't find check", explain) + cmd.exit(1) + } + if check.Analyzer.Doc == "" { + fmt.Fprintln(os.Stderr, explain, "has no documentation") + cmd.exit(1) + } + fmt.Println(check.Doc) + fmt.Println("Online documentation\n https://staticcheck.io/docs/checks#" + check.Analyzer.Name) + cmd.exit(0) + case cmd.flags.merge: + type problemDescriptor struct { + Position token.Position + End token.Position + Category string + Message string + } + type run struct { + checkedFiles map[string]struct{} + problems map[problemDescriptor]struct{} + } + runs := make([]run, len(cmd.flags.fs.Args())) + var allProblems []problem + for i, path := range cmd.flags.fs.Args() { + runs[i] = run{ + checkedFiles: map[string]struct{}{}, + problems: map[problemDescriptor]struct{}{}, + } + + // OPT(dh): should we parallelize this? + err := func(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + var bin binaryOutput + if err := gob.NewDecoder(f).Decode(&bin); err != nil { + return err + } + + for _, cf := range bin.CheckedFiles { + runs[i].checkedFiles[cf] = struct{}{} + } + allProblems = append(allProblems, bin.Problems...) + for _, p := range bin.Problems { + pd := problemDescriptor{ + Position: p.Position, + End: p.End, + Category: p.Category, + Message: p.Message, + } + runs[i].problems[pd] = struct{}{} + } + return nil + }(path) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("couldn't parse file %s: %s", path, err)) + cmd.exit(1) } - if check.Analyzer.Doc == "" { - fmt.Fprintln(os.Stderr, explain, "has no documentation") - exit(1) + } + + relevantProblems := make([]problem, 0, len(allProblems)) + for _, p := range allProblems { + switch p.MergeIf { + case lint.MergeIfAny: + relevantProblems = append(relevantProblems, p) + case lint.MergeIfAll: + doPrint := true + for _, r := range runs { + if _, ok := r.checkedFiles[p.Position.Filename]; ok { + pd := problemDescriptor{ + Position: p.Position, + End: p.End, + Category: p.Category, + Message: p.Message, + } + if _, ok := r.problems[pd]; !ok { + doPrint = false + } + } + } + if doPrint { + relevantProblems = append(relevantProblems, p) + } } - fmt.Println(check.Doc) - fmt.Println("Online documentation\n https://staticcheck.io/docs/checks#" + check.Analyzer.Name) - exit(0) } - } else { + cmd.printDiagnostics(cs, relevantProblems) + default: // Validate that the tags argument is well-formed. go/packages // doesn't detect malformed build flags and returns unhelpful // errors. tf := buildutil.TagsFlag{} if err := tf.Set(cmd.flags.tags); err != nil { fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", cmd.flags.tags, err)) - exit(1) + cmd.exit(1) } - var f formatter switch cmd.flags.formatter { - case "text": - f = textFormatter{W: os.Stdout} - case "stylish": - f = &stylishFormatter{W: os.Stdout} - case "json": - f = jsonFormatter{W: os.Stdout} - case "sarif": - f = &sarifFormatter{ - driverName: cmd.name, - driverVersion: cmd.version, - } - if cmd.name == "staticcheck" { - f.(*sarifFormatter).driverName = "Staticcheck" - f.(*sarifFormatter).driverWebsite = "https://staticcheck.io" - } - - case "null": - f = nullFormatter{} + case "text", "stylish", "json", "sarif", "binary", "null": default: fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter) - exit(2) + cmd.exit(2) } res, err := doLint(cs, cmd.flags.fs.Args(), &options{ @@ -338,61 +390,153 @@ func (cmd *Command) Run() { }) if err != nil { fmt.Fprintln(os.Stderr, err) - exit(1) + cmd.exit(1) } for _, w := range res.Warnings { fmt.Fprintln(os.Stderr, "warning:", w) } - var ( - numErrors int - numWarnings int - numIgnored int - ) + if cmd.flags.formatter == "binary" { + bin := binaryOutput{ + CheckedFiles: res.CheckedFiles, + Problems: res.Problems, + } + err := gob.NewEncoder(os.Stdout).Encode(bin) + if err != nil { + fmt.Fprintf(os.Stderr, "failed writing output: %s\n", err) + cmd.exit(2) + } + cmd.exit(0) + } else { + cmd.printDiagnostics(cs, res.Problems) + } + } +} - fail := cmd.flags.fail - analyzerNames := make([]string, len(cs)) - for i, a := range cs { - analyzerNames[i] = a.Analyzer.Name +func (cmd *Command) exit(code int) { + if cmd.flags.debugCpuprofile != "" { + pprof.StopCPUProfile() + } + if path := cmd.flags.debugMemprofile; path != "" { + f, err := os.Create(path) + if err != nil { + panic(err) } - shouldExit := filterAnalyzerNames(analyzerNames, fail) - shouldExit["staticcheck"] = true - shouldExit["compile"] = true + runtime.GC() + pprof.WriteHeapProfile(f) + } + if cmd.flags.debugTrace != "" { + trace.Stop() + } + os.Exit(code) +} + +func (cmd *Command) printDiagnostics(cs []*lint.Analyzer, problems []problem) { + if len(problems) > 1 { + sort.Slice(problems, func(i, j int) bool { + pi := problems[i].Position + pj := problems[j].Position - notIgnored := make([]problem, 0, len(res.Problems)) - for _, p := range res.Problems { - if p.Category == "compile" && cmd.flags.debugNoCompileErrors { - continue + if pi.Filename != pj.Filename { + return pi.Filename < pj.Filename } - if p.Severity == severityIgnored && !cmd.flags.showIgnored { - numIgnored++ - continue + if pi.Line != pj.Line { + return pi.Line < pj.Line } - if shouldExit[p.Category] { - numErrors++ - } else { - p.Severity = severityWarning - numWarnings++ + if pi.Column != pj.Column { + return pi.Column < pj.Column } - notIgnored = append(notIgnored, p) + + return problems[i].Message < problems[j].Message + }) + + var filtered []problem + filtered = append(filtered, problems[0]) + for i, p := range problems[1:] { + // We may encounter duplicate problems because one file + // can be part of many packages. + if !problems[i].equal(p) { + filtered = append(filtered, p) + } + } + + problems = filtered + } + + var f formatter + switch cmd.flags.formatter { + case "text": + f = textFormatter{W: os.Stdout} + case "stylish": + f = &stylishFormatter{W: os.Stdout} + case "json": + f = jsonFormatter{W: os.Stdout} + case "sarif": + f = &sarifFormatter{ + driverName: cmd.name, + driverVersion: cmd.version, } + if cmd.name == "staticcheck" { + f.(*sarifFormatter).driverName = "Staticcheck" + f.(*sarifFormatter).driverWebsite = "https://staticcheck.io" + } + case "binary": + fmt.Fprintln(os.Stderr, "'-f binary' not supported in this context") + cmd.exit(2) + case "null": + f = nullFormatter{} + default: + fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter) + cmd.exit(2) + } - f.Format(cs, notIgnored) - if f, ok := f.(statter); ok { - f.Stats(len(res.Problems), numErrors, numWarnings, numIgnored) + fail := cmd.flags.fail + analyzerNames := make([]string, len(cs)) + for i, a := range cs { + analyzerNames[i] = a.Analyzer.Name + } + shouldExit := filterAnalyzerNames(analyzerNames, fail) + shouldExit["staticcheck"] = true + shouldExit["compile"] = true + + var ( + numErrors int + numWarnings int + numIgnored int + ) + notIgnored := make([]problem, 0, len(problems)) + for _, p := range problems { + if p.Category == "compile" && cmd.flags.debugNoCompileErrors { + continue + } + if p.Severity == severityIgnored && !cmd.flags.showIgnored { + numIgnored++ + continue + } + if shouldExit[p.Category] { + numErrors++ + } else { + p.Severity = severityWarning + numWarnings++ } + notIgnored = append(notIgnored, p) + } - if numErrors > 0 { - if _, ok := f.(*sarifFormatter); ok { - // When emitting SARIF, finding errors is considered success. - exit(0) - } else { - exit(1) - } + f.Format(cs, notIgnored) + if f, ok := f.(statter); ok { + f.Stats(len(problems), numErrors, numWarnings, numIgnored) + } + + if numErrors > 0 { + if _, ok := f.(*sarifFormatter); ok { + // When emitting SARIF, finding errors is considered success. + cmd.exit(0) + } else { + cmd.exit(1) } - exit(0) } + cmd.exit(0) } func usage(name string, fs *flag.FlagSet) func() { diff --git a/lintcmd/lint.go b/lintcmd/lint.go index 9b54c532c..1e391e5db 100644 --- a/lintcmd/lint.go +++ b/lintcmd/lint.go @@ -10,7 +10,6 @@ import ( "os/signal" "path/filepath" "regexp" - "sort" "strconv" "strings" "time" @@ -30,7 +29,7 @@ import ( // A linter lints Go source code. type linter struct { - Checkers []*lint.Analyzer + Checkers []*lint.Analyzer // TODO rename to Analyzers Runner *runner.Runner } @@ -104,9 +103,12 @@ func (l *linter) Lint(cfg *packages.Config, patterns []string) (LintResult, erro } } + analyzerByName := map[string]*lint.Analyzer{} analyzerNames := make([]string, len(l.Checkers)) for i, a := range l.Checkers { - analyzerNames[i] = a.Analyzer.Name + name := a.Analyzer.Name + analyzerByName[name] = a + analyzerNames[i] = name } used := map[unusedKey]bool{} @@ -127,10 +129,7 @@ func (l *linter) Lint(cfg *packages.Config, patterns []string) (LintResult, erro continue } - for _, f := range res.Package.GoFiles { - out.CheckedFiles = append(out.CheckedFiles, f) - } - + out.CheckedFiles = append(out.CheckedFiles, res.Package.GoFiles...) allowedAnalyzers := filterAnalyzerNames(analyzerNames, res.Config.Checks) resd, err := res.Load() if err != nil { @@ -143,6 +142,12 @@ func (l *linter) Lint(cfg *packages.Config, patterns []string) (LintResult, erro } out.Problems = append(out.Problems, filtered...) + // OPT move this code into the 'success' function. + for i, p := range out.Problems { + a := analyzerByName[p.Category] + out.Problems[i].MergeIf = a.Doc.MergeIf + } + for _, obj := range resd.Unused.Used { // FIXME(dh): pick the object whose filename does not include $GOROOT key := unusedKey{ @@ -184,40 +189,10 @@ func (l *linter) Lint(cfg *packages.Config, patterns []string) (LintResult, erro Message: fmt.Sprintf("%s %s is unused", uo.obj.Kind, uo.obj.Name), Category: "U1000", }, + MergeIf: lint.MergeIfAll, }) } - if len(out.Problems) == 0 { - return out, nil - } - - sort.Slice(out.Problems, func(i, j int) bool { - pi := out.Problems[i].Position - pj := out.Problems[j].Position - - if pi.Filename != pj.Filename { - return pi.Filename < pj.Filename - } - if pi.Line != pj.Line { - return pi.Line < pj.Line - } - if pi.Column != pj.Column { - return pi.Column < pj.Column - } - - return out.Problems[i].Message < out.Problems[j].Message - }) - - var filtered []problem - filtered = append(filtered, out.Problems[0]) - for i, p := range out.Problems[1:] { - // We may encounter duplicate problems because one file - // can be part of many packages. - if !out.Problems[i].equal(p) { - filtered = append(filtered, p) - } - } - out.Problems = filtered return out, nil } @@ -349,6 +324,7 @@ func (s severity) String() string { type problem struct { runner.Diagnostic Severity severity + MergeIf lint.MergeStrategy } func (p problem) equal(o problem) bool { @@ -356,7 +332,8 @@ func (p problem) equal(o problem) bool { p.End == o.End && p.Message == o.Message && p.Category == o.Category && - p.Severity == o.Severity + p.Severity == o.Severity && + p.MergeIf == o.MergeIf } func (p *problem) String() string { diff --git a/simple/doc.go b/simple/doc.go index 06b788b41..11cc70c3a 100644 --- a/simple/doc.go +++ b/simple/doc.go @@ -19,7 +19,8 @@ case x := <-ch: x := <-ch fmt.Println(x) `, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1001": { @@ -33,6 +34,9 @@ for i, x := range src { }`, After: `copy(dst, src)`, Since: "2017.1", + // MergeIfAll because the types of src and dst might be different under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, "S1002": { @@ -40,20 +44,25 @@ for i, x := range src { Before: `if x == true {}`, After: `if x {}`, Since: "2017.1", + // MergeIfAll because 'true' might not be the builtin constant under all build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, "S1003": { - Title: `Replace call to \'strings.Index\' with \'strings.Contains\'`, - Before: `if strings.Index(x, y) != -1 {}`, - After: `if strings.Contains(x, y) {}`, - Since: "2017.1", + Title: `Replace call to \'strings.Index\' with \'strings.Contains\'`, + Before: `if strings.Index(x, y) != -1 {}`, + After: `if strings.Contains(x, y) {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1004": { - Title: `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`, - Before: `if bytes.Compare(x, y) == 0 {}`, - After: `if bytes.Equal(x, y) {}`, - Since: "2017.1", + Title: `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`, + Before: `if bytes.Compare(x, y) == 0 {}`, + After: `if bytes.Equal(x, y) {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1005": { @@ -67,13 +76,15 @@ _ = <-ch`, for range s{} x = someMap[key] <-ch`, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1006": { - Title: `Use \"for { ... }\" for infinite loops`, - Text: `For infinite loops, using \'for { ... }\' is the most idiomatic choice.`, - Since: "2017.1", + Title: `Use \"for { ... }\" for infinite loops`, + Text: `For infinite loops, using \'for { ... }\' is the most idiomatic choice.`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1007": { @@ -84,9 +95,10 @@ freely, without the need of escaping. Since regular expressions have their own escape sequences, raw strings can improve their readability.`, - Before: `regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")`, - After: "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)", - Since: "2017.1", + Before: `regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")`, + After: "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1008": { @@ -96,8 +108,9 @@ if { return true } return false`, - After: `return `, - Since: "2017.1", + After: `return `, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1009": { @@ -105,16 +118,18 @@ return false`, Text: `The \'len\' function is defined for all slices, even nil ones, which have a length of zero. It is not necessary to check if a slice is not nil before checking that its length is not zero.`, - Before: `if x != nil && len(x) != 0 {}`, - After: `if len(x) != 0 {}`, - Since: "2017.1", + Before: `if x != nil && len(x) != 0 {}`, + After: `if len(x) != 0 {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1010": { Title: `Omit default slice index`, Text: `When slicing, the second index defaults to the length of the value, making \'s[n:len(s)]\' and \'s[n:]\' equivalent.`, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1011": { @@ -125,15 +140,18 @@ for _, e := range y { }`, After: `x = append(x, y...)`, Since: "2017.1", + // MergeIfAll because y might not be a slice under all build tags. + MergeIf: lint.MergeIfAll, }, "S1012": { Title: `Replace \'time.Now().Sub(x)\' with \'time.Since(x)\'`, Text: `The \'time.Since\' helper has the same effect as using \'time.Now().Sub(x)\' but is easier to read.`, - Before: `time.Now().Sub(x)`, - After: `time.Since(x)`, - Since: "2017.1", + Before: `time.Now().Sub(x)`, + After: `time.Since(x)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1016": { @@ -152,7 +170,8 @@ y := T2{ After: ` var x T1 y := T2(x)`, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAll, }, "S1017": { @@ -166,8 +185,9 @@ mistakes.`, if strings.HasPrefix(str, prefix) { str = str[len(prefix):] }`, - After: `str = strings.TrimPrefix(str, prefix)`, - Since: "2017.1", + After: `str = strings.TrimPrefix(str, prefix)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1018": { @@ -180,8 +200,9 @@ slice.`, for i := 0; i < n; i++ { bs[i] = bs[offset+i] }`, - After: `copy(bs[:n], bs[offset:])`, - Since: "2017.1", + After: `copy(bs[:n], bs[offset:])`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1019": { @@ -190,13 +211,17 @@ for i := 0; i < n; i++ { arguments. For channels, the length defaults to zero, and for slices, the capacity defaults to the length.`, Since: "2017.1", + // MergeIfAll because the type might be different under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, "S1020": { - Title: `Omit redundant nil check in type assertion`, - Before: `if _, ok := i.(T); ok && i != nil {}`, - After: `if _, ok := i.(T); ok {}`, - Since: "2017.1", + Title: `Omit redundant nil check in type assertion`, + Before: `if _, ok := i.(T); ok && i != nil {}`, + After: `if _, ok := i.(T); ok {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1021": { @@ -204,8 +229,9 @@ the capacity defaults to the length.`, Before: ` var x uint x = 1`, - After: `var x uint = 1`, - Since: "2017.1", + After: `var x uint = 1`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1023": { @@ -216,16 +242,18 @@ the final statement of the function. Switches in Go do not have automatic fallthrough, unlike languages like C. It is not necessary to have a break statement as the final statement in a case block.`, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1024": { Title: `Replace \'x.Sub(time.Now())\' with \'time.Until(x)\'`, Text: `The \'time.Until\' helper has the same effect as using \'x.Sub(time.Now())\' but is easier to read.`, - Before: `x.Sub(time.Now())`, - After: `time.Until(x)`, - Since: "2017.1", + Before: `x.Sub(time.Now())`, + After: `time.Until(x)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1025": { @@ -258,14 +286,16 @@ to string(y) z.String() `, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAll, }, "S1028": { - Title: `Simplify error construction with \'fmt.Errorf\'`, - Before: `errors.New(fmt.Sprintf(...))`, - After: `fmt.Errorf(...)`, - Since: "2017.1", + Title: `Simplify error construction with \'fmt.Errorf\'`, + Before: `errors.New(fmt.Sprintf(...))`, + After: `fmt.Errorf(...)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1029": { @@ -275,9 +305,10 @@ isn't used, this is functionally equivalent to converting the string to a slice of runes and ranging over that. Ranging directly over the string will be more performant, however, as it avoids allocating a new slice, the size of which depends on the length of the string.`, - Before: `for _, r := range []rune(s) {}`, - After: `for _, r := range s {}`, - Since: "2017.1", + Before: `for _, r := range []rune(s) {}`, + After: `for _, r := range s {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1030": { @@ -289,7 +320,8 @@ use the other method. The only exception to this are map lookups. Due to a compiler optimization, \'m[string(buf.Bytes())]\' is more efficient than \'m[buf.String()]\'. `, - Since: "2017.1", + Since: "2017.1", + MergeIf: lint.MergeIfAny, }, "S1031": { @@ -308,6 +340,9 @@ for _, x := range s { ... }`, Since: "2017.1", + // MergeIfAll because x might be a channel under some build tags. + // you shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, "S1032": { @@ -315,20 +350,23 @@ for _, x := range s { Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\' and \'sort.Sort(sort.StringSlice(x))\'.`, - Before: `sort.Sort(sort.StringSlice(x))`, - After: `sort.Strings(x)`, - Since: "2019.1", + Before: `sort.Sort(sort.StringSlice(x))`, + After: `sort.Strings(x)`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, }, "S1033": { - Title: `Unnecessary guard around call to \"delete\"`, - Text: `Calling \'delete\' on a nil map is a no-op.`, - Since: "2019.2", + Title: `Unnecessary guard around call to \"delete\"`, + Text: `Calling \'delete\' on a nil map is a no-op.`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, }, "S1034": { - Title: `Use result of type assertion to simplify cases`, - Since: "2019.2", + Title: `Use result of type assertion to simplify cases`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, }, "S1035": { @@ -336,7 +374,8 @@ and \'sort.Sort(sort.StringSlice(x))\'.`, Text: ` The methods on \'net/http.Header\', namely \'Add\', \'Del\', \'Get\' and \'Set\', already canonicalize the given header name.`, - Since: "2020.1", + Since: "2020.1", + MergeIf: lint.MergeIfAny, }, "S1036": { @@ -371,7 +410,8 @@ can be simplified to m["k"] += 4 `, - Since: "2020.1", + Since: "2020.1", + MergeIf: lint.MergeIfAny, }, "S1037": { @@ -379,13 +419,15 @@ can be simplified to Text: `Using a select statement with a single case receiving from the result of \'time.After\' is a very elaborate way of sleeping that can much simpler be expressed with a simple call to time.Sleep.`, - Since: "2020.1", + Since: "2020.1", + MergeIf: lint.MergeIfAny, }, "S1038": { - Title: "Unnecessarily complex way of printing formatted string", - Text: `Instead of using \'fmt.Print(fmt.Sprintf(...))\', one can use \'fmt.Printf(...)\'.`, - Since: "2020.1", + Title: "Unnecessarily complex way of printing formatted string", + Text: `Instead of using \'fmt.Print(fmt.Sprintf(...))\', one can use \'fmt.Printf(...)\'.`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, }, "S1039": { @@ -394,6 +436,9 @@ can much simpler be expressed with a simple call to time.Sleep.`, Calling \'fmt.Sprint\' with a single string argument is unnecessary and identical to using the string directly.`, Since: "2020.1", + // MergeIfAll because s might not be a string under all build tags. + // you shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, "S1040": { Title: "Type assertion to current type", @@ -404,5 +449,8 @@ delete the type assertion. If you want to check that \'x\' is not nil, consider being explicit and using an actual \'if x == nil\' comparison instead of relying on the type assertion panicking.`, Since: "2021.1", + // MergeIfAll because x might have different types under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, }, }) diff --git a/staticcheck/doc.go b/staticcheck/doc.go index f04782459..5c107cded 100644 --- a/staticcheck/doc.go +++ b/staticcheck/doc.go @@ -9,18 +9,21 @@ var Docs = lint.Markdownify(map[string]*lint.RawDocumentation{ Title: `Invalid regular expression`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1001": { Title: `Invalid template`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1002": { Title: `Invalid format in \'time.Parse\'`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1003": { @@ -33,6 +36,7 @@ serializing maps, channels, strings, or functions. Before Go 1.8, \'bool\' wasn't supported, either.`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1004": { @@ -51,6 +55,7 @@ If you truly meant to sleep for a tiny amount of time, use for some amount of nanoseconds.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1005": { @@ -75,6 +80,7 @@ Windows, will have a \'/bin/sh\' program: exec.Command("/bin/sh", "-c", "ls | grep Awesome")`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1006": { @@ -100,12 +106,14 @@ input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb and pass the string as an argument.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1007": { Title: `Invalid URL in \'net/url.Parse\'`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1008": { @@ -132,6 +140,7 @@ The easiest way of obtaining the canonical form of a key is to use \'http.CanonicalHeaderKey\'.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1010": { @@ -140,36 +149,42 @@ The easiest way of obtaining the canonical form of a key is to use return all results, specify a negative number.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants }, "SA1011": { Title: `Various methods in the \"strings\" package expect valid UTF-8, but invalid input is provided`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1012": { Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1013": { Title: `\'io.Seeker.Seek\' is being called with the whence constant as the first argument, but it should be the second`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1014": { Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1015": { Title: `Using \'time.Tick\' in a way that will leak. Consider using \'time.NewTicker\', and only use \'time.Tick\' in tests, commands and endless functions`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1016": { @@ -180,6 +195,7 @@ never passed to the process, but instead handled directly by the kernel. It is therefore pointless to try and handle these signals.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1017": { @@ -192,6 +208,7 @@ appropriate size. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1018": { @@ -200,18 +217,21 @@ signal value, a buffer of size 1 is sufficient.`, instances, use a negative number, or use \'strings.ReplaceAll\'.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants }, "SA1019": { Title: `Using a deprecated function, variable, constant or field`, Since: "2017.1", Severity: lint.SeverityDeprecated, + MergeIf: lint.MergeIfAny, }, "SA1020": { Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1021": { @@ -223,6 +243,7 @@ order to correctly compare two \'net.IP\'s, the \'net.IP.Equal\' method should be used, as it takes both representations into account.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1023": { @@ -230,6 +251,7 @@ be used, as it takes both representations into account.`, Text: `\'Write\' must not modify the slice data, even temporarily.`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1024": { @@ -246,18 +268,21 @@ will result in the string \'"word"\' – any characters that are 1, 2, 3 or In order to remove one string from another, use \'strings.TrimPrefix\' instead.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1025": { Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`, Since: "2019.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1026": { Title: `Cannot marshal channels or functions`, Since: "2019.2", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1027": { @@ -271,6 +296,7 @@ You can use the structlayout tool to inspect the alignment of fields in a struct.`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1028": { @@ -278,6 +304,7 @@ in a struct.`, Text: `The first argument of \'sort.Slice\' must be a slice.`, Since: "2020.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA1029": { @@ -293,6 +320,7 @@ exported context key variables' static type should be a pointer or interface.`, Since: "2020.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA1030": { @@ -301,12 +329,14 @@ interface.`, the various parsing and formatting functions in \'strconv\'.`, Since: "2021.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA2000": { Title: `\'sync.WaitGroup.Add\' called inside the goroutine, leading to a race condition`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA2001": { @@ -329,18 +359,21 @@ comments with a \'//lint:ignore\' directive can be used to suppress this rare false positive.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA2002": { Title: `Called \'testing.T.FailNow\' or \'SkipNow\' in a goroutine, which isn't allowed`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA2003": { Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA3000": { @@ -353,6 +386,7 @@ the usual way of implementing \'TestMain\' is to end it with \'os.Exit(m.Run())\'.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA3001": { @@ -363,114 +397,133 @@ single operation. Benchmark code must not alter \'b.N\' as this would falsify results.`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA4000": { Title: `Binary operator has identical expressions on both sides`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4001": { Title: `\'&*x\' gets simplified to \'x\', it does not copy \'x\'`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4003": { Title: `Comparing unsigned values against negative values is pointless`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4004": { Title: `The loop exits unconditionally after one iteration`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4005": { Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`, Since: "2021.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4006": { Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4008": { Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4009": { Title: `A function argument is overwritten before its first use`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4010": { Title: `The result of \'append\' will never be observed anywhere`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4011": { Title: `Break statement with no effect. Did you mean to break out of an outer loop?`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4012": { Title: `Comparing a value against NaN even though no value is equal to NaN`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4013": { Title: `Negating a boolean twice (\'!!b\') is the same as writing \'b\'. This is either redundant, or a typo.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4014": { Title: `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4015": { Title: `Calling functions like \'math.Ceil\' on floats converted from integers doesn't do anything useful`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4016": { Title: `Certain bitwise operations, such as \'x ^ 0\', do not do anything useful`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants }, "SA4017": { Title: `A pure function's return value is discarded, making the call pointless`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4018": { Title: `Self-assignment of variables`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4019": { Title: `Multiple, identical build constraints in the same file`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4020": { @@ -543,12 +596,14 @@ the following type switch will have an unreachable case clause: and therefore \'doSomething()\''s return value implements both.`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA4021": { Title: `\"x = append(y)\" is equivalent to \"x = y\"`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4022": { @@ -556,6 +611,7 @@ and therefore \'doSomething()\''s return value implements both.`, Text: `Code such as \"if &x == nil\" is meaningless, because taking the address of a variable always yields a non-nil pointer.`, Since: "2020.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4023": { @@ -619,6 +675,7 @@ https://golang.org/doc/faq#nil_error, licensed under the Creative Commons Attribution 3.0 License.`, Since: "2020.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // TODO should this be MergeIfAll? }, "SA4024": { @@ -634,6 +691,7 @@ Example: }`, Since: "2021.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4025": { @@ -654,6 +712,7 @@ division involving named constants, to avoid noisy positives. `, Since: "2021.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4026": { @@ -669,6 +728,7 @@ To explicitly and reliably create a negative zero, you can use the \'math.Copysign\' function: \'math.Copysign(0, -1)\'.`, Since: "2021.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4027": { @@ -682,12 +742,14 @@ As a consequence, the following code pattern is an expensive no-op: \'u.Query().Add(key, value)\'.`, Since: "2021.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4028": { Title: `\'x % 1\' is always zero`, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants }, "SA4029": { @@ -702,6 +764,7 @@ but there are more convenient helpers, namely \'sort.Float64s\', `, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4030": { @@ -714,30 +777,35 @@ don't include \'n\'. \'rand.Intn(1)\' therefore doesn't generate \'0\' or \'1\', it always generates \'0\'.`, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA4031": { Title: `Checking never-nil value against nil`, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5000": { Title: `Assignment to nil map`, Since: "2017.1", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA5001": { Title: `Deferring \'Close\' before checking for a possible error`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5002": { Title: `The empty for loop (\"for {}\") spins and can block the scheduler`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5003": { @@ -747,12 +815,14 @@ block. In a function that never returns, i.e. one containing an infinite loop, defers will never execute.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5004": { Title: `\"for { select { ...\" with an empty default branch spins`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5005": { @@ -770,6 +840,7 @@ on the object. That way, the number of references can temporarily go to zero before the object is being passed to the finalizer.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5007": { @@ -785,18 +856,21 @@ safe to use. Go, however, does not implement TCO, and as such a loop should be used instead.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5008": { Title: `Invalid struct tag`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5009": { Title: `Invalid Printf call`, Since: "2019.2", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA5010": { @@ -820,6 +894,10 @@ either.`, Since: "2020.1", Severity: lint.SeverityWarning, + // Technically this should be MergeIfAll, but the Go compiler + // already flags some impossible type assertions, so + // MergeIfAny is consistent with the compiler. + MergeIf: lint.MergeIfAny, }, "SA5011": { @@ -889,6 +967,7 @@ logrus. Please file an issue if we're missing support for a popular package.`, Since: "2020.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA5012": { @@ -899,12 +978,14 @@ For example, \'strings.NewReplacer\' takes pairs of old and new strings, and calling it with an odd number of elements would be an error.`, Since: "2020.2", Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, }, "SA6000": { Title: `Using \'regexp.Match\' or related in a loop, should use \'regexp.Compile\'`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA6001": { @@ -936,6 +1017,7 @@ For some history on this optimization, check out commit f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA6002": { @@ -954,6 +1036,7 @@ See the comments on https://go-review.googlesource.com/c/go/+/24371 that discuss this problem.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA6003": { @@ -977,6 +1060,7 @@ first one yields byte offsets, while the second one yields indices in the slice of runes.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA6005": { @@ -1002,24 +1086,28 @@ For a more in-depth explanation of this issue, see https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9001": { Title: `Defers in range loops may not run when you expect them to`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9002": { Title: `Using a non-octal \'os.FileMode\' that looks like it was meant to be in octal.`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9003": { Title: `Empty body in an if or else branch`, Since: "2017.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9004": { @@ -1113,6 +1201,7 @@ This code will output as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`, Since: "2019.1", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9005": { @@ -1127,6 +1216,7 @@ marshaling behavior, e.g. via \'MarshalJSON\' methods. It will also not flag empty structs.`, Since: "2019.2", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, }, "SA9006": { @@ -1152,6 +1242,10 @@ positives in somewhat exotic but valid bit twiddling tricks: }`, Since: "2020.2", Severity: lint.SeverityWarning, + // Technically this should be MergeIfAll, because the type of + // v might be different for different build tags. Practically, + // don't write code that depends on that. + MergeIf: lint.MergeIfAny, }, "SA9007": { @@ -1179,6 +1273,7 @@ This check flags attempts at deleting the following directories: `, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, "SA9008": { @@ -1202,5 +1297,6 @@ value \'0\' and the type \'int\'. `, Since: "Unreleased", Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, }, }) diff --git a/stylecheck/doc.go b/stylecheck/doc.go index 63005411f..7ac707ee2 100644 --- a/stylecheck/doc.go +++ b/stylecheck/doc.go @@ -17,6 +17,7 @@ the guidelines laid out in https://github.com/golang/go/wiki/CodeReviewComments#package-comments.`, Since: "2019.1", NonDefault: true, + MergeIf: lint.MergeIfAny, }, "ST1001": { @@ -47,6 +48,7 @@ Quoting Go Code Review Comments: > current package or in an imported package.`, Since: "2019.1", Options: []string{"dot_import_whitelist"}, + MergeIf: lint.MergeIfAny, }, "ST1003": { @@ -62,6 +64,7 @@ See the following links for details: Since: "2019.1", NonDefault: true, Options: []string{"initialisms"}, + MergeIf: lint.MergeIfAny, }, "ST1005": { @@ -77,7 +80,8 @@ Quoting Go Code Review Comments: > \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so > that \'log.Printf("Reading %s: %v", filename, err)\' formats without a > spurious capital letter mid-message.`, - Since: "2019.1", + Since: "2019.1", + MergeIf: lint.MergeIfAny, }, "ST1006": { @@ -95,13 +99,15 @@ Quoting Go Code Review Comments: > almost every line of every method of the type; familiarity admits > brevity. Be consistent, too: if you call the receiver "c" in one > method, don't call it "cl" in another.`, - Since: "2019.1", + Since: "2019.1", + MergeIf: lint.MergeIfAny, }, "ST1008": { - Title: `A function's error value should be its last return value`, - Text: `A function's error value should be its last return value.`, - Since: `2019.1`, + Title: `A function's error value should be its last return value`, + Text: `A function's error value should be its last return value.`, + Since: `2019.1`, + MergeIf: lint.MergeIfAny, }, "ST1011": { @@ -111,14 +117,16 @@ as a count of nanoseconds. An expression like \'5 * time.Microsecond\' yields the value \'5000\'. It is therefore not appropriate to suffix a variable of type \'time.Duration\' with any time unit, such as \'Msec\' or \'Milli\'.`, - Since: `2019.1`, + Since: `2019.1`, + MergeIf: lint.MergeIfAny, }, "ST1012": { Title: `Poorly chosen name for error variable`, Text: `Error variables that are part of an API should be called \'errFoo\' or \'ErrFoo\'.`, - Since: "2019.1", + Since: "2019.1", + MergeIf: lint.MergeIfAny, }, "ST1013": { @@ -131,17 +139,20 @@ instead of hard-coding magic numbers, to vastly improve the readability of your code.`, Since: "2019.1", Options: []string{"http_status_code_whitelist"}, + MergeIf: lint.MergeIfAny, }, "ST1015": { - Title: `A switch's default case should be the first or last case`, - Since: "2019.1", + Title: `A switch's default case should be the first or last case`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, }, "ST1016": { Title: `Use consistent method receiver names`, Since: "2019.1", NonDefault: true, + MergeIf: lint.MergeIfAny, }, "ST1017": { @@ -151,12 +162,14 @@ literal is on the left side of the comparison. These are a common idiom in languages in which assignment is an expression, to avoid bugs of the kind \"if (x = 42)\". In Go, which doesn't allow for this kind of bug, we prefer the more idiomatic \"if x == 42\".`, - Since: "2019.2", + Since: "2019.2", + MergeIf: lint.MergeIfAny, }, "ST1018": { - Title: `Avoid zero-width and control characters in string literals`, - Since: "2019.2", + Title: `Avoid zero-width and control characters in string literals`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, }, "ST1019": { @@ -182,7 +195,8 @@ intentionally (see for example https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d) – if you want to allow this pattern in your code base, you're advised to disable this check.`, - Since: "2020.1", + Since: "2020.1", + MergeIf: lint.MergeIfAny, }, "ST1020": { @@ -200,6 +214,7 @@ See https://golang.org/doc/effective_go.html#commentary for more information on how to write good documentation.`, Since: "2020.1", NonDefault: true, + MergeIf: lint.MergeIfAny, }, "ST1021": { @@ -217,6 +232,7 @@ See https://golang.org/doc/effective_go.html#commentary for more information on how to write good documentation.`, Since: "2020.1", NonDefault: true, + MergeIf: lint.MergeIfAny, }, "ST1022": { @@ -234,11 +250,13 @@ See https://golang.org/doc/effective_go.html#commentary for more information on how to write good documentation.`, Since: "2020.1", NonDefault: true, + MergeIf: lint.MergeIfAny, }, "ST1023": { Title: "Redundant type in variable declaration", Since: "2021.1", NonDefault: true, + MergeIf: lint.MergeIfAll, }, })