diff --git a/.golangci.yml b/.golangci.yml index ac5890604946..49ff84bcfa76 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -178,7 +178,7 @@ issues: - path: pkg/golinters/godot/godot.go linters: [staticcheck] text: "SA1019: settings.CheckAll is deprecated: use Scope instead" - - path: pkg/golinters/gci/gci.go + - path: pkg/goformatters/gci/gci.go linters: [staticcheck] text: "SA1019: settings.LocalPrefixes is deprecated: use Sections instead." - path: pkg/golinters/mnd/mnd.go diff --git a/go.mod b/go.mod index 47399e359c05..7030f2f87620 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/gofrs/flock v0.12.1 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a github.com/golangci/go-printf-func-name v0.1.0 - github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d github.com/golangci/misspell v0.6.0 github.com/golangci/plugin-module-register v0.1.1 github.com/golangci/revgrep v0.5.3 @@ -95,7 +95,6 @@ require ( github.com/sashamelentyev/interfacebloat v1.1.0 github.com/sashamelentyev/usestdlibvars v1.28.0 github.com/securego/gosec/v2 v2.21.4 - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c github.com/shirou/gopsutil/v4 v4.24.12 github.com/sirupsen/logrus v1.9.3 github.com/sivchari/containedctx v1.0.3 diff --git a/go.sum b/go.sum index 486852b11f71..b0570ef00b60 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 h1:t5wybL6RtO83VwoMOb7U/Peqe3gGKQlPIC66wXmnkvM= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9/go.mod h1:Ag3L7sh7E28qAp/5xnpMMTuGYqxLZoSaEHZDkZB1RgU= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= @@ -494,8 +494,6 @@ github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxX github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= diff --git a/pkg/goformatters/analyzer.go b/pkg/goformatters/analyzer.go new file mode 100644 index 000000000000..14f8929b20aa --- /dev/null +++ b/pkg/goformatters/analyzer.go @@ -0,0 +1,55 @@ +package goformatters + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + + "github.com/rogpeppe/go-internal/diff" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/golangci/golangci-lint/pkg/goformatters/internal" + "github.com/golangci/golangci-lint/pkg/logutils" +) + +// NewAnalyzer converts a [Formatter] to an [analysis.Analyzer]. +func NewAnalyzer(logger logutils.Log, doc string, formatter Formatter) *analysis.Analyzer { + return &analysis.Analyzer{ + Name: formatter.Name(), + Doc: doc, + Run: func(pass *analysis.Pass) (any, error) { + for _, file := range pass.Files { + position, isGoFiles := goanalysis.GetGoFilePosition(pass, file) + if !isGoFiles { + continue + } + + input, err := os.ReadFile(position.Filename) + if err != nil { + return nil, fmt.Errorf("unable to open file %s: %w", position.Filename, err) + } + + output, err := formatter.Format(position.Filename, input) + if err != nil { + return nil, fmt.Errorf("error while running %s: %w", formatter.Name(), err) + } + + if !bytes.Equal(input, output) { + newName := filepath.ToSlash(position.Filename) + oldName := newName + ".orig" + + theDiff := diff.Diff(oldName, input, newName, output) + + err = internal.ExtractDiagnosticFromPatch(pass, file, string(theDiff), logger) + if err != nil { + return nil, fmt.Errorf("can't extract issues from %s diff output %q: %w", formatter.Name(), string(theDiff), err) + } + } + } + + return nil, nil + }, + } +} diff --git a/pkg/goformatters/gci/gci.go b/pkg/goformatters/gci/gci.go index bae39f3cee6d..590f8da2a0e6 100644 --- a/pkg/goformatters/gci/gci.go +++ b/pkg/goformatters/gci/gci.go @@ -1,11 +1,15 @@ package gci import ( + "fmt" + gcicfg "github.com/daixiang0/gci/pkg/config" "github.com/daixiang0/gci/pkg/gci" + "github.com/daixiang0/gci/pkg/log" "github.com/ldez/grignotin/gomod" "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goformatters/internal" ) const Name = "gci" @@ -14,23 +18,36 @@ type Formatter struct { config *gcicfg.Config } -func New(cfg config.GciSettings) (*Formatter, error) { +func New(settings *config.GciSettings) (*Formatter, error) { + log.InitLogger() + _ = log.L().Sync() + modPath, err := gomod.GetModulePath() if err != nil { - return nil, err + internal.FormatterLogger.Errorf("gci: %v", err) } - parsedCfg, err := gcicfg.YamlConfig{ + cfg := gcicfg.YamlConfig{ Cfg: gcicfg.BoolConfig{ - NoInlineComments: cfg.NoInlineComments, - NoPrefixComments: cfg.NoPrefixComments, - SkipGenerated: cfg.SkipGenerated, - CustomOrder: cfg.CustomOrder, - NoLexOrder: cfg.NoLexOrder, + NoInlineComments: settings.NoInlineComments, + NoPrefixComments: settings.NoPrefixComments, + SkipGenerated: settings.SkipGenerated, + CustomOrder: settings.CustomOrder, + NoLexOrder: settings.NoLexOrder, }, - SectionStrings: cfg.Sections, + SectionStrings: settings.Sections, ModPath: modPath, - }.Parse() + } + + if settings.LocalPrefixes != "" { + cfg.SectionStrings = []string{ + "standard", + "default", + fmt.Sprintf("prefix(%s)", settings.LocalPrefixes), + } + } + + parsedCfg, err := cfg.Parse() if err != nil { return nil, err } diff --git a/pkg/goformatters/gofmt/gofmt.go b/pkg/goformatters/gofmt/gofmt.go index fe2e355590ed..9005c751d2e2 100644 --- a/pkg/goformatters/gofmt/gofmt.go +++ b/pkg/goformatters/gofmt/gofmt.go @@ -12,18 +12,18 @@ type Formatter struct { options gofmt.Options } -func New(cfg config.GoFmtSettings) *Formatter { - var rewriteRules []gofmt.RewriteRule - for _, rule := range cfg.RewriteRules { - rewriteRules = append(rewriteRules, gofmt.RewriteRule(rule)) - } +func New(settings *config.GoFmtSettings) *Formatter { + options := gofmt.Options{} + + if settings != nil { + options.NeedSimplify = settings.Simplify - return &Formatter{ - options: gofmt.Options{ - NeedSimplify: cfg.Simplify, - RewriteRules: rewriteRules, - }, + for _, rule := range settings.RewriteRules { + options.RewriteRules = append(options.RewriteRules, gofmt.RewriteRule(rule)) + } } + + return &Formatter{options: options} } func (*Formatter) Name() string { diff --git a/pkg/goformatters/gofumpt/gofumpt.go b/pkg/goformatters/gofumpt/gofumpt.go index f4605e2333d7..7c548a2afde8 100644 --- a/pkg/goformatters/gofumpt/gofumpt.go +++ b/pkg/goformatters/gofumpt/gofumpt.go @@ -14,14 +14,18 @@ type Formatter struct { options gofumpt.Options } -func New(cfg config.GofumptSettings, goVersion string) *Formatter { - return &Formatter{ - options: gofumpt.Options{ +func New(settings *config.GofumptSettings, goVersion string) *Formatter { + var options gofumpt.Options + + if settings != nil { + options = gofumpt.Options{ LangVersion: getLangVersion(goVersion), - ModulePath: cfg.ModulePath, - ExtraRules: cfg.ExtraRules, - }, + ModulePath: settings.ModulePath, + ExtraRules: settings.ExtraRules, + } } + + return &Formatter{options: options} } func (*Formatter) Name() string { @@ -32,7 +36,6 @@ func (f *Formatter) Format(_ string, src []byte) ([]byte, error) { return gofumpt.Source(src, f.options) } -// modified copy of pkg/golinters/gofumpt/gofumpt.go func getLangVersion(v string) string { if v == "" { // TODO: defaults to "1.15", in the future (v2) must be removed. diff --git a/pkg/goformatters/goimports/goimports.go b/pkg/goformatters/goimports/goimports.go index add9eb3a61a2..fa0f1fc4f36e 100644 --- a/pkg/goformatters/goimports/goimports.go +++ b/pkg/goformatters/goimports/goimports.go @@ -2,13 +2,19 @@ package goimports import ( "golang.org/x/tools/imports" + + "github.com/golangci/golangci-lint/pkg/config" ) const Name = "goimports" type Formatter struct{} -func New() *Formatter { +func New(settings *config.GoImportsSettings) *Formatter { + if settings != nil { + imports.LocalPrefix = settings.LocalPrefixes + } + return &Formatter{} } diff --git a/pkg/goformatters/internal/commons.go b/pkg/goformatters/internal/commons.go new file mode 100644 index 000000000000..53b26328d82a --- /dev/null +++ b/pkg/goformatters/internal/commons.go @@ -0,0 +1,6 @@ +package internal + +import "github.com/golangci/golangci-lint/pkg/logutils" + +// FormatterLogger must be use only when the context logger is not available. +var FormatterLogger = logutils.NewStderrLog(logutils.DebugKeyFormatter) diff --git a/pkg/golinters/internal/diff.go b/pkg/goformatters/internal/diff.go similarity index 97% rename from pkg/golinters/internal/diff.go rename to pkg/goformatters/internal/diff.go index 8e5e1f0e735e..6147d0015380 100644 --- a/pkg/golinters/internal/diff.go +++ b/pkg/goformatters/internal/diff.go @@ -12,7 +12,6 @@ import ( "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" ) @@ -215,7 +214,7 @@ func ExtractDiagnosticFromPatch( pass *analysis.Pass, file *ast.File, patch string, - lintCtx *linter.Context, + logger logutils.Log, ) error { diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch)) if err != nil { @@ -232,12 +231,12 @@ func ExtractDiagnosticFromPatch( for _, d := range diffs { if len(d.Hunks) == 0 { - lintCtx.Log.Warnf("Got no hunks in diff %+v", d) + logger.Warnf("Got no hunks in diff %+v", d) continue } for _, hunk := range d.Hunks { - p := hunkChangesParser{log: lintCtx.Log} + p := hunkChangesParser{log: logger} changes := p.parse(hunk) diff --git a/pkg/golinters/internal/diff_test.go b/pkg/goformatters/internal/diff_test.go similarity index 100% rename from pkg/golinters/internal/diff_test.go rename to pkg/goformatters/internal/diff_test.go diff --git a/pkg/golinters/internal/testdata/add_only.diff b/pkg/goformatters/internal/testdata/add_only.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only.diff rename to pkg/goformatters/internal/testdata/add_only.diff diff --git a/pkg/golinters/internal/testdata/add_only_different_lines.diff b/pkg/goformatters/internal/testdata/add_only_different_lines.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only_different_lines.diff rename to pkg/goformatters/internal/testdata/add_only_different_lines.diff diff --git a/pkg/golinters/internal/testdata/add_only_in_all_diff.diff b/pkg/goformatters/internal/testdata/add_only_in_all_diff.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only_in_all_diff.diff rename to pkg/goformatters/internal/testdata/add_only_in_all_diff.diff diff --git a/pkg/golinters/internal/testdata/add_only_multiple_lines.diff b/pkg/goformatters/internal/testdata/add_only_multiple_lines.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only_multiple_lines.diff rename to pkg/goformatters/internal/testdata/add_only_multiple_lines.diff diff --git a/pkg/golinters/internal/testdata/add_only_on_first_line.diff b/pkg/goformatters/internal/testdata/add_only_on_first_line.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only_on_first_line.diff rename to pkg/goformatters/internal/testdata/add_only_on_first_line.diff diff --git a/pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff b/pkg/goformatters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff similarity index 100% rename from pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff rename to pkg/goformatters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff diff --git a/pkg/golinters/internal/testdata/delete_last_line.diff b/pkg/goformatters/internal/testdata/delete_last_line.diff similarity index 100% rename from pkg/golinters/internal/testdata/delete_last_line.diff rename to pkg/goformatters/internal/testdata/delete_last_line.diff diff --git a/pkg/golinters/internal/testdata/delete_only_first_lines.diff b/pkg/goformatters/internal/testdata/delete_only_first_lines.diff similarity index 100% rename from pkg/golinters/internal/testdata/delete_only_first_lines.diff rename to pkg/goformatters/internal/testdata/delete_only_first_lines.diff diff --git a/pkg/golinters/internal/testdata/gofmt_diff.diff b/pkg/goformatters/internal/testdata/gofmt_diff.diff similarity index 100% rename from pkg/golinters/internal/testdata/gofmt_diff.diff rename to pkg/goformatters/internal/testdata/gofmt_diff.diff diff --git a/pkg/golinters/internal/testdata/replace_line.diff b/pkg/goformatters/internal/testdata/replace_line.diff similarity index 100% rename from pkg/golinters/internal/testdata/replace_line.diff rename to pkg/goformatters/internal/testdata/replace_line.diff diff --git a/pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff b/pkg/goformatters/internal/testdata/replace_line_after_first_line_adding.diff similarity index 100% rename from pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff rename to pkg/goformatters/internal/testdata/replace_line_after_first_line_adding.diff diff --git a/pkg/goformatters/meta_formatter.go b/pkg/goformatters/meta_formatter.go index e07b3aad8055..d66878c7abe5 100644 --- a/pkg/goformatters/meta_formatter.go +++ b/pkg/goformatters/meta_formatter.go @@ -23,20 +23,20 @@ func NewMetaFormatter(log logutils.Log, cfg *config.Config, enabledLinters map[s m := &MetaFormatter{log: log} if _, ok := enabledLinters[gofmt.Name]; ok { - m.formatters = append(m.formatters, gofmt.New(cfg.LintersSettings.Gofmt)) + m.formatters = append(m.formatters, gofmt.New(&cfg.LintersSettings.Gofmt)) } if _, ok := enabledLinters[gofumpt.Name]; ok { - m.formatters = append(m.formatters, gofumpt.New(cfg.LintersSettings.Gofumpt, cfg.Run.Go)) + m.formatters = append(m.formatters, gofumpt.New(&cfg.LintersSettings.Gofumpt, cfg.Run.Go)) } if _, ok := enabledLinters[goimports.Name]; ok { - m.formatters = append(m.formatters, goimports.New()) + m.formatters = append(m.formatters, goimports.New(&cfg.LintersSettings.Goimports)) } // gci is a last because the only goal of gci is to handle imports. if _, ok := enabledLinters[gci.Name]; ok { - formatter, err := gci.New(cfg.LintersSettings.Gci) + formatter, err := gci.New(&cfg.LintersSettings.Gci) if err != nil { return nil, fmt.Errorf("gci: creating formatter: %w", err) } diff --git a/pkg/golinters/gci/gci.go b/pkg/golinters/gci/gci.go index 841ee81b0de5..b79f1a370cc3 100644 --- a/pkg/golinters/gci/gci.go +++ b/pkg/golinters/gci/gci.go @@ -1,116 +1,33 @@ package gci import ( - "bytes" - "fmt" - "io" - "os" - - gcicfg "github.com/daixiang0/gci/pkg/config" - "github.com/daixiang0/gci/pkg/gci" - "github.com/daixiang0/gci/pkg/log" - "github.com/shazow/go-diff/difflib" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/golangci/golangci-lint/pkg/goformatters" + gcibase "github.com/golangci/golangci-lint/pkg/goformatters/gci" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" ) const linterName = "gci" -type differ interface { - Diff(out io.Writer, a io.ReadSeeker, b io.ReadSeeker) error -} - func New(settings *config.GciSettings) *goanalysis.Linter { - log.InitLogger() - _ = log.L().Sync() - - diff := difflib.New() - - a := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, + formatter, err := gcibase.New(settings) + if err != nil { + internal.LinterLogger.Fatalf("%s: create analyzer: %v", linterName, err) } + a := goformatters.NewAnalyzer( + internal.LinterLogger.Child(linterName), + "Checks if code and import statements are formatted, with additional rules.", + formatter, + ) + return goanalysis.NewLinter( - linterName, - "Checks if code and import statements are formatted, it makes import statements always deterministic.", + a.Name, + a.Doc, []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - a.Run = func(pass *analysis.Pass) (any, error) { - err := run(lintCtx, pass, settings, diff) - if err != nil { - return nil, err - } - - return nil, nil - } - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func run(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GciSettings, diff differ) error { - cfg := gcicfg.YamlConfig{ - Cfg: gcicfg.BoolConfig{ - NoInlineComments: settings.NoInlineComments, - NoPrefixComments: settings.NoPrefixComments, - SkipGenerated: settings.SkipGenerated, - CustomOrder: settings.CustomOrder, - NoLexOrder: settings.NoLexOrder, - }, - SectionStrings: settings.Sections, - ModPath: pass.Module.Path, - } - - if settings.LocalPrefixes != "" { - cfg.SectionStrings = []string{ - "standard", - "default", - fmt.Sprintf("prefix(%s)", settings.LocalPrefixes), - } - } - - parsedCfg, err := cfg.Parse() - if err != nil { - return err - } - - for _, file := range pass.Files { - position, isGoFile := goanalysis.GetGoFilePosition(pass, file) - if !isGoFile { - continue - } - - input, err := os.ReadFile(position.Filename) - if err != nil { - return fmt.Errorf("unable to open file %s: %w", position.Filename, err) - } - - _, output, err := gci.LoadFormat(input, position.Filename, *parsedCfg) - if err != nil { - return fmt.Errorf("error while running gci: %w", err) - } - - if !bytes.Equal(input, output) { - out := bytes.NewBufferString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", position.Filename)) - - err := diff.Diff(out, bytes.NewReader(input), bytes.NewReader(output)) - if err != nil { - return fmt.Errorf("error while running gci: %w", err) - } - - diff := out.String() - - err = internal.ExtractDiagnosticFromPatch(pass, file, diff, lintCtx) - if err != nil { - return fmt.Errorf("can't extract issues from gci diff output %q: %w", diff, err) - } - } - } - - return nil + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/gci/testdata/gci.go b/pkg/golinters/gci/testdata/gci.go index f8cd3a21c50a..ad0fc8ee9366 100644 --- a/pkg/golinters/gci/testdata/gci.go +++ b/pkg/golinters/gci/testdata/gci.go @@ -2,9 +2,9 @@ //golangcitest:config_path testdata/gci.yml package testdata -import ( // want "File is not properly formatted" - "golang.org/x/tools/go/analysis" - "github.com/golangci/golangci-lint/pkg/config" // want "File is not properly formatted" +import ( + "golang.org/x/tools/go/analysis" // want "File is not properly formatted" + "github.com/golangci/golangci-lint/pkg/config" "fmt" "errors" gcicfg "github.com/daixiang0/gci/pkg/config" diff --git a/pkg/golinters/gci/testdata/gci_cgo.go b/pkg/golinters/gci/testdata/gci_cgo.go index c82fc829f3e9..17997222958d 100644 --- a/pkg/golinters/gci/testdata/gci_cgo.go +++ b/pkg/golinters/gci/testdata/gci_cgo.go @@ -12,10 +12,9 @@ package testdata */ import "C" -import ( // want "File is not properly formatted" - "golang.org/x/tools/go/analysis" - "github.com/golangci/golangci-lint/pkg/config" // want "File is not properly formatted" - "unsafe" +import ( + "golang.org/x/tools/go/analysis" // want "File is not properly formatted" + "github.com/golangci/golangci-lint/pkg/config" "fmt" "errors" gcicfg "github.com/daixiang0/gci/pkg/config" diff --git a/pkg/golinters/gofmt/gofmt.go b/pkg/golinters/gofmt/gofmt.go index b6531d5314af..221224294906 100644 --- a/pkg/golinters/gofmt/gofmt.go +++ b/pkg/golinters/gofmt/gofmt.go @@ -1,68 +1,28 @@ package gofmt import ( - "fmt" - - gofmtAPI "github.com/golangci/gofmt/gofmt" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/golangci/golangci-lint/pkg/goformatters" + gofmtbase "github.com/golangci/golangci-lint/pkg/goformatters/gofmt" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" ) const linterName = "gofmt" func New(settings *config.GoFmtSettings) *goanalysis.Linter { - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } + a := goformatters.NewAnalyzer( + internal.LinterLogger.Child(linterName), + "Checks if the code is formatted according to 'gofmt' command.", + gofmtbase.New(settings), + ) return goanalysis.NewLinter( - linterName, - "Checks if the code is formatted according to 'gofmt' command.", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - err := runGofmt(lintCtx, pass, settings) - if err != nil { - return nil, err - } - - return nil, nil - } - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runGofmt(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoFmtSettings) error { - var rewriteRules []gofmtAPI.RewriteRule - for _, rule := range settings.RewriteRules { - rewriteRules = append(rewriteRules, gofmtAPI.RewriteRule(rule)) - } - - for _, file := range pass.Files { - position, isGoFile := goanalysis.GetGoFilePosition(pass, file) - if !isGoFile { - continue - } - - diff, err := gofmtAPI.RunRewrite(position.Filename, settings.Simplify, rewriteRules) - if err != nil { // TODO: skip - return err - } - if diff == nil { - continue - } - - err = internal.ExtractDiagnosticFromPatch(pass, file, string(diff), lintCtx) - if err != nil { - return fmt.Errorf("can't extract issues from gofmt diff output %q: %w", string(diff), err) - } - } - - return nil + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/gofumpt/gofumpt.go b/pkg/golinters/gofumpt/gofumpt.go index 7a11a9074d03..878a5c79b0aa 100644 --- a/pkg/golinters/gofumpt/gofumpt.go +++ b/pkg/golinters/gofumpt/gofumpt.go @@ -1,106 +1,28 @@ package gofumpt import ( - "bytes" - "fmt" - "io" - "os" - "strings" - - "github.com/shazow/go-diff/difflib" "golang.org/x/tools/go/analysis" - "mvdan.cc/gofumpt/format" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/golangci/golangci-lint/pkg/goformatters" + gofumptbase "github.com/golangci/golangci-lint/pkg/goformatters/gofumpt" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" ) const linterName = "gofumpt" -type differ interface { - Diff(out io.Writer, a io.ReadSeeker, b io.ReadSeeker) error -} - func New(settings *config.GofumptSettings) *goanalysis.Linter { - diff := difflib.New() - - var options format.Options - - if settings != nil { - options = format.Options{ - LangVersion: getLangVersion(settings), - ModulePath: settings.ModulePath, - ExtraRules: settings.ExtraRules, - } - } - - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } + a := goformatters.NewAnalyzer( + internal.LinterLogger.Child(linterName), + "Checks if code and import statements are formatted, with additional rules.", + gofumptbase.New(settings, settings.LangVersion), + ) return goanalysis.NewLinter( - linterName, - "Checks if code and import statements are formatted, with additional rules.", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - err := runGofumpt(lintCtx, pass, diff, options) - if err != nil { - return nil, err - } - - return nil, nil - } - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runGofumpt(lintCtx *linter.Context, pass *analysis.Pass, diff differ, options format.Options) error { - for _, file := range pass.Files { - position, isGoFile := goanalysis.GetGoFilePosition(pass, file) - if !isGoFile { - continue - } - - input, err := os.ReadFile(position.Filename) - if err != nil { - return fmt.Errorf("unable to open file %s: %w", position.Filename, err) - } - - output, err := format.Source(input, options) - if err != nil { - return fmt.Errorf("error while running gofumpt: %w", err) - } - - if !bytes.Equal(input, output) { - out := bytes.NewBufferString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", position.Filename)) - - err := diff.Diff(out, bytes.NewReader(input), bytes.NewReader(output)) - if err != nil { - return fmt.Errorf("error while running gofumpt: %w", err) - } - - diff := out.String() - - err = internal.ExtractDiagnosticFromPatch(pass, file, diff, lintCtx) - if err != nil { - return fmt.Errorf("can't extract issues from gofumpt diff output %q: %w", diff, err) - } - } - } - - return nil -} - -func getLangVersion(settings *config.GofumptSettings) string { - if settings == nil || settings.LangVersion == "" { - // TODO: defaults to "1.15", in the future (v2) must be removed. - return "go1.15" - } - - return "go" + strings.TrimPrefix(settings.LangVersion, "go") + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/gofumpt/testdata/gofumpt.go b/pkg/golinters/gofumpt/testdata/gofumpt.go index 79cec0a0d8a1..24dce876649e 100644 --- a/pkg/golinters/gofumpt/testdata/gofumpt.go +++ b/pkg/golinters/gofumpt/testdata/gofumpt.go @@ -5,4 +5,4 @@ import "fmt" func GofumptNewLine() { fmt.Println( "foo" ) // want "File is not properly formatted" -} // want "File is not properly formatted" \ No newline at end of file +} \ No newline at end of file diff --git a/pkg/golinters/goimports/goimports.go b/pkg/golinters/goimports/goimports.go index 6ddc9a75b1f2..d7ba98559acf 100644 --- a/pkg/golinters/goimports/goimports.go +++ b/pkg/golinters/goimports/goimports.go @@ -1,66 +1,28 @@ package goimports import ( - "fmt" - - goimportsAPI "github.com/golangci/gofmt/goimports" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/imports" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" + "github.com/golangci/golangci-lint/pkg/goformatters" + goimportsbase "github.com/golangci/golangci-lint/pkg/goformatters/goimports" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" ) const linterName = "goimports" func New(settings *config.GoImportsSettings) *goanalysis.Linter { - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - } + a := goformatters.NewAnalyzer( + internal.LinterLogger.Child(linterName), + "Checks if the code and import statements are formatted according to the 'goimports' command.", + goimportsbase.New(settings), + ) return goanalysis.NewLinter( - linterName, - "Checks if the code and import statements are formatted according to the 'goimports' command.", - []*analysis.Analyzer{analyzer}, + a.Name, + a.Doc, + []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - imports.LocalPrefix = settings.LocalPrefixes - - analyzer.Run = func(pass *analysis.Pass) (any, error) { - err := runGoImports(lintCtx, pass) - if err != nil { - return nil, err - } - - return nil, nil - } - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runGoImports(lintCtx *linter.Context, pass *analysis.Pass) error { - for _, file := range pass.Files { - position, isGoFile := goanalysis.GetGoFilePosition(pass, file) - if !isGoFile { - continue - } - - diff, err := goimportsAPI.Run(position.Filename) - if err != nil { // TODO: skip - return err - } - if diff == nil { - continue - } - - err = internal.ExtractDiagnosticFromPatch(pass, file, string(diff), lintCtx) - if err != nil { - return fmt.Errorf("can't extract issues from goimports diff output %q: %w", string(diff), err) - } - } - - return nil + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/logutils/logutils.go b/pkg/logutils/logutils.go index 3c27e2557af1..fcda4dcc92b9 100644 --- a/pkg/logutils/logutils.go +++ b/pkg/logutils/logutils.go @@ -25,9 +25,10 @@ const ( DebugKeyExcludeRules = "exclude_rules" DebugKeyExec = "exec" DebugKeyFilenameUnadjuster = "filename_unadjuster" - DebugKeyInvalidIssue = "invalid_issue" DebugKeyForbidigo = "forbidigo" + DebugKeyFormatter = "formatter" DebugKeyGoEnv = "goenv" + DebugKeyInvalidIssue = "invalid_issue" DebugKeyLinter = "linter" DebugKeyLintersContext = "linters_context" DebugKeyLintersDB = "lintersdb" diff --git a/pkg/result/processors/max_per_file_from_linter.go b/pkg/result/processors/max_per_file_from_linter.go index a39c9847349e..3ec49f3acfad 100644 --- a/pkg/result/processors/max_per_file_from_linter.go +++ b/pkg/result/processors/max_per_file_from_linter.go @@ -20,6 +20,7 @@ func NewMaxPerFileFromLinter(cfg *config.Config) *MaxPerFileFromLinter { // otherwise we need to fix all issues in the file at once maxPerFileFromLinterConfig["gofmt"] = 1 maxPerFileFromLinterConfig["goimports"] = 1 + maxPerFileFromLinterConfig["gci"] = 1 } return &MaxPerFileFromLinter{