Skip to content

dev: add suggested migration for linter configuration #5902

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

Merged
merged 1 commit into from
Jun 28, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}

Expand Down
68 changes: 68 additions & 0 deletions pkg/golinters/wsl/wsl.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
}
72 changes: 59 additions & 13 deletions pkg/lint/linter/config.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/lint/lintersdb/builder_linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Loading