Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lintcmd: make -check case-insensitive, error on unknown checks #1546

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lintcmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,11 @@ func (cmd *Command) printDiagnostics(cs []*lint.Analyzer, diagnostics []diagnost
for i, a := range cs {
analyzerNames[i] = a.Analyzer.Name
}
shouldExit := filterAnalyzerNames(analyzerNames, fail)
shouldExit, err := filterAnalyzerNames(analyzerNames, fail)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return 1
}
shouldExit["staticcheck"] = true
shouldExit["compile"] = true

Expand Down
22 changes: 18 additions & 4 deletions lintcmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os/signal"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -199,7 +200,10 @@ func (l *linter) lint(r *runner.Runner, cfg *packages.Config, patterns []string)
}

out.CheckedFiles = append(out.CheckedFiles, res.Package.GoFiles...)
allowedAnalyzers := filterAnalyzerNames(analyzerNames, res.Config.Checks)
allowedAnalyzers, err := filterAnalyzerNames(analyzerNames, res.Config.Checks)
if err != nil {
return out, err
}
resd, err := res.Load()
if err != nil {
return out, err
Expand Down Expand Up @@ -511,16 +515,17 @@ func defaultGoVersion() string {
return v
}

func filterAnalyzerNames(analyzers []string, checks []string) map[string]bool {
func filterAnalyzerNames(analyzers []string, checks []string) (map[string]bool, error) {
allowedChecks := map[string]bool{}

for _, check := range checks {
check = strings.ToUpper(check)
b := true
if len(check) > 1 && check[0] == '-' {
b = false
check = check[1:]
}
if check == "*" || check == "all" {
if check == "*" || check == "ALL" {
// Match all
for _, c := range analyzers {
allowedChecks[c] = b
Expand All @@ -530,27 +535,36 @@ func filterAnalyzerNames(analyzers []string, checks []string) map[string]bool {
prefix := check[:len(check)-1]
isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1

matched := false
for _, a := range analyzers {
idx := strings.IndexFunc(a, func(r rune) bool { return unicode.IsNumber(r) })
if isCat {
// Glob is S*, which should match S1000 but not SA1000
cat := a[:idx]
if prefix == cat {
matched = true
allowedChecks[a] = b
}
} else {
// Glob is S1*
if strings.HasPrefix(a, prefix) {
matched = true
allowedChecks[a] = b
}
}
}
if !matched {
return nil, fmt.Errorf("%q matched no checks", check)
}
} else {
// Literal check name
if !slices.Contains(analyzers, check) {
return nil, fmt.Errorf("unknown check: %q", check)
}
allowedChecks[check] = b
}
}
return allowedChecks
return allowedChecks, nil
}

var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?`)
Expand Down
80 changes: 80 additions & 0 deletions lintcmd/lint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package lintcmd

import (
"reflect"
"slices"
"strings"
"testing"
)

func TestFilterAnalyzerNames(t *testing.T) {
analyzers := []string{
"S1000", "S1001",
"SA1000", "SA1001",
"SA2000", "SA2001",
"ST1000", "ST1001",
"U1000",
}
all := make(map[string]bool)
for _, a := range analyzers {
all[a] = true
}
allMinus := func(minus ...string) map[string]bool {
m := make(map[string]bool)
for k := range all {
m[k] = !slices.Contains(minus, k)
}
return m
}

tests := []struct {
in []string
want map[string]bool
wantErr string
}{
{[]string{"all"}, all, ""},
{[]string{"All"}, all, ""},
{[]string{"*"}, all, ""},

{[]string{"S*"}, map[string]bool{"S1000": true, "S1001": true}, ""},
{[]string{"SA1*"}, map[string]bool{"SA1000": true, "SA1001": true}, ""},
{[]string{"SA2*"}, map[string]bool{"SA2000": true, "SA2001": true}, ""},
{[]string{"SA*"}, map[string]bool{"SA1000": true, "SA1001": true, "SA2000": true, "SA2001": true}, ""},

{[]string{"S1000", "st1000"}, map[string]bool{"S1000": true, "ST1000": true}, ""},

{[]string{"SA9*"}, nil, "matched no checks"},
{[]string{"S*", "SA9*"}, nil, "matched no checks"},
{[]string{"SA9*", "all"}, nil, "matched no checks"},

{[]string{"S9999"}, nil, "unknown check"},
{[]string{"check"}, nil, "unknown check"},
{[]string{`!@#'"`}, nil, "unknown check"},

{[]string{"all", "-S1000"}, allMinus("S1000"), ""},
{[]string{"all", "-SA1*"}, allMinus("SA1000", "SA1001"), ""},
{[]string{"-S1000", "all"}, all, ""},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
have, haveErr := filterAnalyzerNames(analyzers, tt.in)
if !errorContains(haveErr, tt.wantErr) {
t.Fatalf("wrong error:\nhave: %s\nwant: %s", haveErr, tt.wantErr)
}
if !reflect.DeepEqual(have, tt.want) {
t.Fatalf("\nhave: %v\nwant: %v", have, tt.want)
}
})
}
}

func errorContains(have error, want string) bool {
if have == nil {
return want == ""
}
if want == "" {
return false
}
return strings.Contains(have.Error(), want)
}