From 40d6afb35d631316c29efa3a293f5952f433f95a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 28 Jun 2025 19:32:48 +0200 Subject: [PATCH] dev: add suggested migration for linter configuration --- .golangci.yml | 6 ++- pkg/commands/run.go | 11 +++++ pkg/golinters/wsl/wsl.go | 68 ++++++++++++++++++++++++++ pkg/lint/linter/config.go | 72 +++++++++++++++++++++++----- pkg/lint/lintersdb/builder_linter.go | 3 +- 5 files changed, 145 insertions(+), 15 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 23effe4b53e1..b9da446104fe 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -191,7 +191,11 @@ linters: - path: pkg/lint/lintersdb/builder_linter.go text: "SA1019: wsl.NewV4 is deprecated: use NewV5 instead." linters: - - staticcheck + - staticcheck + - path: pkg/golinters/wsl/wsl.go + text: "SA1019: config.WSLv4Settings is deprecated: use WSLv5Settings instead." + linters: + - staticcheck formatters: enable: diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 1864559f9fc3..8ff8a674c7d1 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -430,6 +430,17 @@ func (c *runCommand) printDeprecatedLinterMessages(enabledLinters map[string]*li } c.log.Warnf("The linter '%s' is deprecated (since %s) due to: %s %s", name, lc.Deprecation.Since, lc.Deprecation.Message, extra) + + if lc.Deprecation.ConfigSuggestion != nil { + suggestion, err := lc.Deprecation.ConfigSuggestion() + if err != nil { + c.log.Errorf("New configuration suggestion error: %v", err) + } + + if suggestion != "" { + c.log.Warnf("Suggested new configuration:\n%s", suggestion) + } + } } } diff --git a/pkg/golinters/wsl/wsl.go b/pkg/golinters/wsl/wsl.go index 11f0122def50..4a5c6e19b458 100644 --- a/pkg/golinters/wsl/wsl.go +++ b/pkg/golinters/wsl/wsl.go @@ -1,10 +1,14 @@ package wsl import ( + "slices" + wslv4 "github.com/bombsimon/wsl/v4" + wslv5 "github.com/bombsimon/wsl/v5" "github.com/golangci/golangci-lint/v2/pkg/config" "github.com/golangci/golangci-lint/v2/pkg/goanalysis" + "github.com/golangci/golangci-lint/v2/pkg/golinters/internal" ) // Deprecated: use NewV5 instead. @@ -35,3 +39,67 @@ func NewV4(settings *config.WSLv4Settings) *goanalysis.Linter { NewLinterFromAnalyzer(wslv4.NewAnalyzer(conf)). WithLoadMode(goanalysis.LoadModeSyntax) } + +// Only used the set YAML struct tags. +type v5YAML struct { + AllowFirstInBlock bool `yaml:"allow-first-in-block"` + AllowWholeBlock bool `yaml:"allow-whole-block"` + BranchMaxLines int `yaml:"branch-max-lines,omitempty"` + CaseMaxLines int `yaml:"case-max-lines,omitempty"` + Enable []string `yaml:"enable,omitempty"` + Disable []string `yaml:"disable,omitempty"` +} + +func Migration(old *config.WSLv4Settings) any { + if old == nil { + return nil + } + + cfg := v5YAML{ + AllowFirstInBlock: true, + AllowWholeBlock: false, + BranchMaxLines: 2, + CaseMaxLines: old.ForceCaseTrailingWhitespaceLimit, + } + + if !old.StrictAppend { + cfg.Disable = append(cfg.Disable, wslv5.CheckAppend.String()) + } + + if old.AllowAssignAndAnythingCuddle { + cfg.Disable = append(cfg.Disable, wslv5.CheckAssign.String()) + } + + if old.AllowMultiLineAssignCuddle { + internal.LinterLogger.Warnf("`allow-multiline-assign` is deprecated and always allowed in wsl >= v5") + } + + if old.AllowTrailingComment { + internal.LinterLogger.Warnf("`allow-trailing-comment` is deprecated and always allowed in wsl >= v5") + } + + if old.AllowSeparatedLeadingComment { + internal.LinterLogger.Warnf("`allow-separated-leading-comment` is deprecated and always allowed in wsl >= v5") + } + + if old.AllowCuddleDeclaration { + cfg.Disable = append(cfg.Disable, wslv5.CheckDecl.String()) + } + + if old.ForceCuddleErrCheckAndAssign { + cfg.Enable = append(cfg.Enable, wslv5.CheckErr.String()) + } + + if old.ForceExclusiveShortDeclarations { + cfg.Enable = append(cfg.Enable, wslv5.CheckAssignExclusive.String()) + } + + if !old.AllowAssignAndCallCuddle { + cfg.Enable = append(cfg.Enable, wslv5.CheckAssignExpr.String()) + } + + slices.Sort(cfg.Enable) + slices.Sort(cfg.Disable) + + return cfg +} diff --git a/pkg/lint/linter/config.go b/pkg/lint/linter/config.go index a7a9a68d9d92..0287dece95d4 100644 --- a/pkg/lint/linter/config.go +++ b/pkg/lint/linter/config.go @@ -1,9 +1,11 @@ package linter import ( + "bytes" "fmt" "golang.org/x/tools/go/packages" + "gopkg.in/yaml.v3" "github.com/golangci/golangci-lint/v2/pkg/config" ) @@ -20,10 +22,11 @@ const ( ) type Deprecation struct { - Since string - Message string - Replacement string - Level DeprecationLevel + Since string + Message string + Replacement string + Level DeprecationLevel + ConfigSuggestion func() (string, error) } type Config struct { @@ -119,22 +122,26 @@ func (lc *Config) WithSince(version string) *Config { return lc } -func (lc *Config) Deprecated(message, version, replacement string, level DeprecationLevel) *Config { +func (lc *Config) Deprecated(message, version string, level DeprecationLevel, opts ...func(*Deprecation)) *Config { lc.Deprecation = &Deprecation{ - Since: version, - Message: message, - Replacement: replacement, - Level: level, + Since: version, + Message: message, + Level: level, + } + + for _, opt := range opts { + opt(lc.Deprecation) } + return lc } -func (lc *Config) DeprecatedWarning(message, version, replacement string) *Config { - return lc.Deprecated(message, version, replacement, DeprecationWarning) +func (lc *Config) DeprecatedWarning(message, version string, opts ...func(*Deprecation)) *Config { + return lc.Deprecated(message, version, DeprecationWarning, opts...) } -func (lc *Config) DeprecatedError(message, version, replacement string) *Config { - return lc.Deprecated(message, version, replacement, DeprecationError) +func (lc *Config) DeprecatedError(message, version string, opts ...func(*Deprecation)) *Config { + return lc.Deprecated(message, version, DeprecationError, opts...) } func (lc *Config) IsDeprecated() bool { @@ -160,6 +167,45 @@ func (lc *Config) WithNoopFallback(cfg *config.Config, cond func(cfg *config.Con return lc } +func Replacement[T any](replacement string, mgr func(T) any, data T) func(*Deprecation) { + return func(d *Deprecation) { + if replacement == "" { + return + } + + d.Replacement = replacement + + if mgr == nil { + return + } + + d.ConfigSuggestion = func() (string, error) { + buf := bytes.NewBuffer([]byte{}) + + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + + suggestion := map[string]any{ + "linters": map[string]any{ + "enable": []string{ + d.Replacement, + }, + "settings": map[string]any{ + d.Replacement: mgr(data), + }, + }, + } + + err := encoder.Encode(suggestion) + if err != nil { + return "", fmt.Errorf("%s: invalid configuration: %w", d.Replacement, err) + } + + return buf.String(), nil + } + } +} + func IsGoLowerThanGo122() func(cfg *config.Config) error { return isGoLowerThanGo("1.22") } diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 60e0c6fb815f..df84ad737966 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -692,7 +692,8 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithURL("https://github.com/tomarrell/wrapcheck"), linter.NewConfig(wsl.NewV4(&cfg.Linters.Settings.WSL)). - DeprecatedWarning("new major version.", "v2.2.0", "wsl_v5"). + DeprecatedWarning("new major version.", "v2.2.0", + linter.Replacement("wsl_v5", wsl.Migration, &cfg.Linters.Settings.WSL)). WithSince("v1.20.0"). WithAutoFix(). WithURL("https://github.com/bombsimon/wsl"),