feat: automatic Go version detection (#2669)
Some checks failed
Release a tag / release (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile.alpine]) (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile]) (push) Has been cancelled

* feat: disable unsupported go1.18 govet analyzers
* fix: inactivate interfacer with go1.18
This commit is contained in:
Ludovic Fernandez 2022-03-23 16:54:11 +01:00 committed by GitHub
parent da0a6b3b8a
commit 7bbbe77e5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 112 additions and 69 deletions

View File

@ -66,8 +66,8 @@ run:
# Define the Go version limit. # Define the Go version limit.
# Mainly related to generics support in go1.18. # Mainly related to generics support in go1.18.
# Default: 1.17 # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17
go: 1.18 go: '1.18'
# output configuration options # output configuration options

View File

@ -130,6 +130,7 @@ issues:
run: run:
timeout: 5m timeout: 5m
go: '1.17' # TODO(ldez): we force to use an old version of Go for the CI and the tests.
skip-dirs: skip-dirs:
- test/testdata_etc - test/testdata_etc
- internal/cache - internal/cache

View File

@ -110,6 +110,10 @@ func NewExecutor(version, commit, date string) *Executor {
e.log.Fatalf("Can't read config: %s", err) e.log.Fatalf("Can't read config: %s", err)
} }
if commandLineCfg.Run.Go == "" && e.cfg.Run.Go == "" {
e.cfg.Run.Go = config.DetectGoVersion()
}
// recreate after getting config // recreate after getting config
e.DBManager = lintersdb.NewManager(e.cfg, e.log).WithCustomLinters() e.DBManager = lintersdb.NewManager(e.cfg, e.log).WithCustomLinters()

View File

@ -95,7 +95,7 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, is
"Modules download mode. If not empty, passed as -mod=<mode> to go tools") "Modules download mode. If not empty, passed as -mod=<mode> to go tools")
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
exitcodes.IssuesFound, wh("Exit code when issues were found")) exitcodes.IssuesFound, wh("Exit code when issues were found"))
fs.StringVar(&rc.Go, "go", "1.17", wh("Targeted Go version")) fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version"))
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags")) fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work")) fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work"))

View File

@ -1,5 +1,13 @@
package config package config
import (
"os"
"strings"
hcversion "github.com/hashicorp/go-version"
"github.com/ldez/gomoddirectives"
)
// 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. cfgDir string // The directory containing the golangci config file.
@ -31,3 +39,32 @@ func NewDefault() *Config {
type Version struct { type Version struct {
Format string `mapstructure:"format"` Format string `mapstructure:"format"`
} }
func IsGreaterThanOrEqualGo118(v string) bool {
v1, err := hcversion.NewVersion(strings.TrimPrefix(v, "go"))
if err != nil {
return false
}
limit, err := hcversion.NewVersion("1.18")
if err != nil {
return false
}
return v1.GreaterThanOrEqual(limit)
}
func DetectGoVersion() string {
file, _ := gomoddirectives.GetModuleFile()
if file != nil && file.Go != nil && file.Go.Version != "" {
return file.Go.Version
}
v := os.Getenv("GOVERSION")
if v != "" {
return v
}
return "1.17"
}

View File

@ -374,7 +374,8 @@ type GoSecSettings struct {
} }
type GovetSettings struct { type GovetSettings struct {
CheckShadowing bool `mapstructure:"check-shadowing"` Go string `mapstructure:"-"`
CheckShadowing bool `mapstructure:"check-shadowing"`
Settings map[string]map[string]interface{} Settings map[string]map[string]interface{}
Enable []string Enable []string
@ -383,7 +384,7 @@ type GovetSettings struct {
DisableAll bool `mapstructure:"disable-all"` DisableAll bool `mapstructure:"disable-all"`
} }
func (cfg GovetSettings) Validate() error { func (cfg *GovetSettings) Validate() error {
if cfg.EnableAll && cfg.DisableAll { if cfg.EnableAll && cfg.DisableAll {
return errors.New("enable-all and disable-all can't be combined") return errors.New("enable-all and disable-all can't be combined")
} }

View File

@ -119,35 +119,18 @@ var (
} }
) )
func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers []*analysis.Analyzer) bool { func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter {
if cfg.EnableAll { var settings map[string]map[string]interface{}
for _, n := range cfg.Disable { if cfg != nil {
if n == name { settings = cfg.Settings
return false
}
}
return true
} }
// Raw for loops should be OK on small slice lengths. return goanalysis.NewLinter(
for _, n := range cfg.Enable { "govet",
if n == name { "Vet examines Go source code and reports suspicious constructs, "+
return true "such as Printf calls whose arguments do not align with the format string",
} analyzersFromConfig(cfg),
} settings,
for _, n := range cfg.Disable { ).WithLoadMode(goanalysis.LoadModeTypesInfo)
if n == name {
return false
}
}
if cfg.DisableAll {
return false
}
for _, a := range defaultAnalyzers {
if a.Name == name {
return true
}
}
return false
} }
func analyzersFromConfig(cfg *config.GovetSettings) []*analysis.Analyzer { func analyzersFromConfig(cfg *config.GovetSettings) []*analysis.Analyzer {
@ -170,16 +153,42 @@ func analyzersFromConfig(cfg *config.GovetSettings) []*analysis.Analyzer {
return enabledAnalyzers return enabledAnalyzers
} }
func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter { func isAnalyzerEnabled(name string, cfg *config.GovetSettings, defaultAnalyzers []*analysis.Analyzer) bool {
var settings map[string]map[string]interface{} if (name == nilness.Analyzer.Name || name == unusedwrite.Analyzer.Name) &&
if cfg != nil { config.IsGreaterThanOrEqualGo118(cfg.Go) {
settings = cfg.Settings return false
} }
return goanalysis.NewLinter(
"govet", if cfg.EnableAll {
"Vet examines Go source code and reports suspicious constructs, "+ for _, n := range cfg.Disable {
"such as Printf calls whose arguments do not align with the format string", if n == name {
analyzersFromConfig(cfg), return false
settings, }
).WithLoadMode(goanalysis.LoadModeTypesInfo) }
return true
}
// Raw for loops should be OK on small slice lengths.
for _, n := range cfg.Enable {
if n == name {
return true
}
}
for _, n := range cfg.Disable {
if n == name {
return false
}
}
if cfg.DisableAll {
return false
}
for _, a := range defaultAnalyzers {
if a.Name == name {
return true
}
}
return false
} }

View File

@ -1,9 +1,6 @@
package linter package linter
import ( import (
"strings"
hcversion "github.com/hashicorp/go-version"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
@ -126,7 +123,7 @@ func (lc *Config) Name() string {
} }
func (lc *Config) WithNoopFallback(cfg *config.Config) *Config { func (lc *Config) WithNoopFallback(cfg *config.Config) *Config {
if isGreaterThanOrEqualGo118(cfg) { if cfg != nil && config.IsGreaterThanOrEqualGo118(cfg.Run.Go) {
lc.Linter = &Noop{ lc.Linter = &Noop{
name: lc.Linter.Name(), name: lc.Linter.Name(),
desc: lc.Linter.Desc(), desc: lc.Linter.Desc(),
@ -134,6 +131,9 @@ func (lc *Config) WithNoopFallback(cfg *config.Config) *Config {
return nil, nil return nil, nil
}, },
} }
lc.LoadMode = 0
return lc.WithLoadFiles()
} }
return lc return lc
@ -145,21 +145,3 @@ func NewConfig(linter Linter) *Config {
} }
return lc.WithLoadFiles() return lc.WithLoadFiles()
} }
func isGreaterThanOrEqualGo118(cfg *config.Config) bool {
if cfg == nil {
return false
}
v1, err := hcversion.NewVersion(strings.TrimPrefix(cfg.Run.Go, "go"))
if err != nil {
return false
}
limit, err := hcversion.NewVersion("1.18")
if err != nil {
return false
}
return v1.GreaterThanOrEqual(limit)
}

View File

@ -22,7 +22,6 @@ type Noop struct {
func (n Noop) Run(_ context.Context, lintCtx *Context) ([]result.Issue, error) { func (n Noop) Run(_ context.Context, lintCtx *Context) ([]result.Issue, error) {
lintCtx.Log.Warnf("%s is disabled because of go1.18."+ lintCtx.Log.Warnf("%s is disabled because of go1.18."+
" If you are not using go1.18, you can set `go: go1.17` in the `run` section."+
" You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.", n.name) " You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.", n.name)
return nil, nil return nil, nil
} }

View File

@ -164,6 +164,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
unusedCfg = &m.cfg.LintersSettings.Unused unusedCfg = &m.cfg.LintersSettings.Unused
varnamelenCfg = &m.cfg.LintersSettings.Varnamelen varnamelenCfg = &m.cfg.LintersSettings.Varnamelen
wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck
if govetCfg != nil {
govetCfg.Go = m.cfg.Run.Go
}
} }
const megacheckName = "megacheck" const megacheckName = "megacheck"
@ -446,7 +450,8 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithLoadForGoAnalysis(). WithLoadForGoAnalysis().
WithPresets(linter.PresetStyle). WithPresets(linter.PresetStyle).
WithURL("https://github.com/mvdan/interfacer"). WithURL("https://github.com/mvdan/interfacer").
Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", ""), Deprecated("The repository of the linter has been archived by the owner.", "v1.38.0", "").
WithNoopFallback(m.cfg),
linter.NewConfig(golinters.NewIreturn(ireturnCfg)). linter.NewConfig(golinters.NewIreturn(ireturnCfg)).
WithSince("v1.43.0"). WithSince("v1.43.0").

View File

@ -43,6 +43,7 @@ func TestFix(t *testing.T) {
t.Parallel() t.Parallel()
args := []string{ args := []string{
"--go=1.17", // TODO(ldez): we force to use an old version of Go for the CI and the tests.
"--disable-all", "--print-issued-lines=false", "--print-linter-name=false", "--out-format=line-number", "--disable-all", "--print-issued-lines=false", "--print-linter-name=false", "--out-format=line-number",
"--allow-parallel-runners", "--fix", "--allow-parallel-runners", "--fix",
input, input,

View File

@ -179,6 +179,7 @@ func saveConfig(t *testing.T, cfg map[string]interface{}) (cfgPath string, finis
func testOneSource(t *testing.T, sourcePath string) { func testOneSource(t *testing.T, sourcePath string) {
args := []string{ args := []string{
"run", "run",
"--go=1.17", // TODO(ldez): we force to use an old version of Go for the CI and the tests.
"--allow-parallel-runners", "--allow-parallel-runners",
"--disable-all", "--disable-all",
"--print-issued-lines=false", "--print-issued-lines=false",

View File

@ -98,7 +98,10 @@ func (r *LintRunner) Run(args ...string) *RunResult {
func (r *LintRunner) RunCommand(command string, args ...string) *RunResult { func (r *LintRunner) RunCommand(command string, args ...string) *RunResult {
r.Install() r.Install()
runArgs := append([]string{command}, "--internal-cmd-test") runArgs := append([]string{command},
"--go=1.17", // TODO(ldez): we force to use an old version of Go for the CI and the tests.
"--internal-cmd-test",
)
runArgs = append(runArgs, args...) runArgs = append(runArgs, args...)
defer func(startedAt time.Time) { defer func(startedAt time.Time) {