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

staticcheck: configuration for staticcheck, gosimple, stylecheck #2017

Merged
merged 1 commit into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions .golangci.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ linters-settings:
gosimple:
# Select the Go version to target. The default is '1.13'.
go: "1.15"
# https://staticcheck.io/docs/options#checks
checks: [ "all" ]

govet:
# report about shadowed variables
Expand Down Expand Up @@ -514,10 +516,21 @@ linters-settings:
staticcheck:
# Select the Go version to target. The default is '1.13'.
go: "1.15"
# https://staticcheck.io/docs/options#checks
checks: [ "all" ]

stylecheck:
# Select the Go version to target. The default is '1.13'.
go: "1.15"
# https://staticcheck.io/docs/options#checks
checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ]
# https://staticcheck.io/docs/options#dot_import_whitelist
dot-import-whitelist:
- fmt
# https://staticcheck.io/docs/options#initialisms
initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ]
# https://staticcheck.io/docs/options#http_status_code_whitelist
http-status-code-whitelist: [ "200", "400", "404", "500" ]

tagliatelle:
# check the struck tag name case
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,15 @@ type RowsErrCheckSettings struct {

type StaticCheckSettings struct {
GoVersion string `mapstructure:"go"`

Checks []string `mapstructure:"checks"`
Initialisms []string `mapstructure:"initialisms"` // only for stylecheck
DotImportWhitelist []string `mapstructure:"dot-import-whitelist"` // only for stylecheck
HTTPStatusCodeWhitelist []string `mapstructure:"http-status-code-whitelist"` // only for stylecheck
}

func (s *StaticCheckSettings) HasConfiguration() bool {
return len(s.Initialisms) > 0 || len(s.HTTPStatusCodeWhitelist) > 0 || len(s.DotImportWhitelist) > 0 || len(s.Checks) > 0
}

type StructCheckSettings struct {
Expand Down
4 changes: 3 additions & 1 deletion pkg/golinters/gosimple.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

func NewGosimple(settings *config.StaticCheckSettings) *goanalysis.Linter {
analyzers := setupStaticCheckAnalyzers(simple.Analyzers, settings)
cfg := staticCheckConfig(settings)

analyzers := setupStaticCheckAnalyzers(simple.Analyzers, getGoVersion(settings), cfg.Checks)

return goanalysis.NewLinter(
"gosimple",
Expand Down
4 changes: 3 additions & 1 deletion pkg/golinters/staticcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

func NewStaticcheck(settings *config.StaticCheckSettings) *goanalysis.Linter {
analyzers := setupStaticCheckAnalyzers(staticcheck.Analyzers, settings)
cfg := staticCheckConfig(settings)

analyzers := setupStaticCheckAnalyzers(staticcheck.Analyzers, getGoVersion(settings), cfg.Checks)

return goanalysis.NewLinter(
"staticcheck",
Expand Down
156 changes: 145 additions & 11 deletions pkg/golinters/staticcheck_common.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,167 @@
package golinters

import (
"strings"
"unicode"

"golang.org/x/tools/go/analysis"
scconfig "honnef.co/go/tools/config"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/logutils"
)

var debugf = logutils.Debug("megacheck")

func setupStaticCheckAnalyzers(m map[string]*analysis.Analyzer, settings *config.StaticCheckSettings) []*analysis.Analyzer {
var ret []*analysis.Analyzer
for _, v := range m {
setAnalyzerGoVersion(v, settings)
ret = append(ret, v)
func getGoVersion(settings *config.StaticCheckSettings) string {
var goVersion string
if settings != nil {
goVersion = settings.GoVersion
}

if goVersion != "" {
return goVersion
}
return ret
}

func setAnalyzerGoVersion(a *analysis.Analyzer, settings *config.StaticCheckSettings) {
// TODO: uses "1.13" for backward compatibility, but in the future (v2) must be set by using build.Default.ReleaseTags like staticcheck.
goVersion := "1.13"
if settings != nil && settings.GoVersion != "" {
goVersion = settings.GoVersion
return "1.13"
}

func setupStaticCheckAnalyzers(src map[string]*analysis.Analyzer, goVersion string, checks []string) []*analysis.Analyzer {
var names []string
for name := range src {
names = append(names, name)
}

filter := filterAnalyzerNames(names, checks)

var ret []*analysis.Analyzer
for name, a := range src {
if filter[name] {
setAnalyzerGoVersion(a, goVersion)
ret = append(ret, a)
}
}

return ret
}

func setAnalyzerGoVersion(a *analysis.Analyzer, goVersion string) {
if v := a.Flags.Lookup("go"); v != nil {
if err := v.Value.Set(goVersion); err != nil {
debugf("Failed to set go version: %s", err)
}
}
}

func staticCheckConfig(settings *config.StaticCheckSettings) *scconfig.Config {
var cfg *scconfig.Config

if settings == nil || !settings.HasConfiguration() {
return &scconfig.Config{
Checks: []string{"*"}, // override for compatibility reason. Must drop in the next major version.
Initialisms: scconfig.DefaultConfig.Initialisms,
DotImportWhitelist: scconfig.DefaultConfig.DotImportWhitelist,
HTTPStatusCodeWhitelist: scconfig.DefaultConfig.HTTPStatusCodeWhitelist,
}
}

cfg = &scconfig.Config{
Checks: settings.Checks,
Initialisms: settings.Initialisms,
DotImportWhitelist: settings.DotImportWhitelist,
HTTPStatusCodeWhitelist: settings.HTTPStatusCodeWhitelist,
}

if len(cfg.Checks) == 0 {
cfg.Checks = append(cfg.Checks, "*") // override for compatibility reason. Must drop in the next major version.
}

if len(cfg.Initialisms) == 0 {
cfg.Initialisms = append(cfg.Initialisms, scconfig.DefaultConfig.Initialisms...)
}

if len(cfg.DotImportWhitelist) == 0 {
cfg.DotImportWhitelist = append(cfg.DotImportWhitelist, scconfig.DefaultConfig.DotImportWhitelist...)
}

if len(cfg.HTTPStatusCodeWhitelist) == 0 {
cfg.HTTPStatusCodeWhitelist = append(cfg.HTTPStatusCodeWhitelist, scconfig.DefaultConfig.HTTPStatusCodeWhitelist...)
}

cfg.Checks = normalizeList(cfg.Checks)
cfg.Initialisms = normalizeList(cfg.Initialisms)
cfg.DotImportWhitelist = normalizeList(cfg.DotImportWhitelist)
cfg.HTTPStatusCodeWhitelist = normalizeList(cfg.HTTPStatusCodeWhitelist)

return cfg
}

// https://github.com/dominikh/go-tools/blob/9bf17c0388a65710524ba04c2d821469e639fdc2/lintcmd/lint.go#L437-L477
// nolint // Keep the original source code.
func filterAnalyzerNames(analyzers []string, checks []string) map[string]bool {
allowedChecks := map[string]bool{}

for _, check := range checks {
b := true
if len(check) > 1 && check[0] == '-' {
b = false
check = check[1:]
}

if check == "*" || check == "all" {
// Match all
for _, c := range analyzers {
allowedChecks[c] = b
}
} else if strings.HasSuffix(check, "*") {
// Glob
prefix := check[:len(check)-1]
isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1

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 {
allowedChecks[a] = b
}
} else {
// Glob is S1*
if strings.HasPrefix(a, prefix) {
allowedChecks[a] = b
}
}
}
} else {
// Literal check name
allowedChecks[check] = b
}
}
return allowedChecks
}

// https://github.com/dominikh/go-tools/blob/9bf17c0388a65710524ba04c2d821469e639fdc2/config/config.go#L95-L116
func normalizeList(list []string) []string {
if len(list) > 1 {
nlist := make([]string, 0, len(list))
nlist = append(nlist, list[0])
for i, el := range list[1:] {
if el != list[i] {
nlist = append(nlist, el)
}
}
list = nlist
}

for _, el := range list {
if el == "inherit" {
// This should never happen, because the default config
// should not use "inherit"
panic(`unresolved "inherit"`)
}
}

return list
}
13 changes: 12 additions & 1 deletion pkg/golinters/stylecheck.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package golinters

import (
"golang.org/x/tools/go/analysis"
scconfig "honnef.co/go/tools/config"
"honnef.co/go/tools/stylecheck"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
)

func NewStylecheck(settings *config.StaticCheckSettings) *goanalysis.Linter {
analyzers := setupStaticCheckAnalyzers(stylecheck.Analyzers, settings)
cfg := staticCheckConfig(settings)

// `scconfig.Analyzer` is a singleton, then it's not possible to have more than one instance for all staticcheck "sub-linters".
// When we will merge the 4 "sub-linters", the problem will disappear: https://github.com/golangci/golangci-lint/issues/357
// Currently only stylecheck analyzer has a configuration in staticcheck.
scconfig.Analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
return cfg, nil
}

analyzers := setupStaticCheckAnalyzers(stylecheck.Analyzers, getGoVersion(settings), cfg.Checks)

return goanalysis.NewLinter(
"stylecheck",
Expand Down
2 changes: 1 addition & 1 deletion pkg/golinters/unused.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewUnused(settings *config.StaticCheckSettings) *goanalysis.Linter {
},
}

setAnalyzerGoVersion(analyzer, settings)
setAnalyzerGoVersion(analyzer, getGoVersion(settings))

lnt := goanalysis.NewLinter(
name,
Expand Down