diff --git a/go.mod b/go.mod index 190e191d4dfc..40c33426c817 100644 --- a/go.mod +++ b/go.mod @@ -158,7 +158,6 @@ require ( github.com/go-toolsmith/typep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/modinfo v0.3.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect diff --git a/go.sum b/go.sum index 844ecaf11716..8b836eecf02f 100644 --- a/go.sum +++ b/go.sum @@ -240,8 +240,6 @@ github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 h1:t5wybL6RtO83VwoM github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9/go.mod h1:Ag3L7sh7E28qAp/5xnpMMTuGYqxLZoSaEHZDkZB1RgU= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/modinfo v0.3.3 h1:YBQDZpDMJpe5mtd0klUFYL8tSVkmF3cmm0fZ48sc7+s= -github.com/golangci/modinfo v0.3.3/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= diff --git a/pkg/golinters/gci/gci.go b/pkg/golinters/gci/gci.go index dbbf4ca619ee..39ca4100ae28 100644 --- a/pkg/golinters/gci/gci.go +++ b/pkg/golinters/gci/gci.go @@ -1,61 +1,116 @@ package gci import ( + "bytes" "fmt" - "strings" + "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/golinters/gci/internal" + "github.com/golangci/golangci-lint/pkg/golinters/internal" + "github.com/golangci/golangci-lint/pkg/lint/linter" ) const linterName = "gci" -const prefixSeparator = "ยค" +type differ interface { + Diff(out io.Writer, a io.ReadSeeker, b io.ReadSeeker) error +} func New(settings *config.GciSettings) *goanalysis.Linter { - a := internal.NewAnalyzer() - - var cfg map[string]map[string]any - if settings != nil { - var sections []string - for _, section := range settings.Sections { - if strings.HasPrefix(section, "prefix(") { - sections = append(sections, strings.ReplaceAll(section, ",", prefixSeparator)) - continue + log.InitLogger() + _ = log.L().Sync() + + diff := difflib.New() + + a := &analysis.Analyzer{ + Name: linterName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: goanalysis.DummyRun, + } + + return goanalysis.NewLinter( + linterName, + 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 } - sections = append(sections, section) + 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, + } - cfg = map[string]map[string]any{ - a.Name: { - internal.NoInlineCommentsFlag: settings.NoInlineComments, - internal.NoPrefixCommentsFlag: settings.NoPrefixComments, - internal.SkipGeneratedFlag: settings.SkipGenerated, - internal.SectionsFlag: sections, // bug because prefix contains comas. - internal.CustomOrderFlag: settings.CustomOrder, - internal.NoLexOrderFlag: settings.NoLexOrder, - internal.PrefixDelimiterFlag: prefixSeparator, - }, + if settings.LocalPrefixes != "" { + cfg.SectionStrings = []string{ + "standard", + "default", + fmt.Sprintf("prefix(%s)", settings.LocalPrefixes), } + } + + parsedCfg, err := cfg.Parse() + if err != nil { + return err + } - if settings.LocalPrefixes != "" { - prefix := []string{ - "standard", - "default", - fmt.Sprintf("prefix(%s)", strings.Join(strings.Split(settings.LocalPrefixes, ","), prefixSeparator)), + 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) } - cfg[a.Name][internal.SectionsFlag] = prefix } } - return goanalysis.NewLinter( - linterName, - a.Doc, - []*analysis.Analyzer{a}, - cfg, - ).WithLoadMode(goanalysis.LoadModeSyntax) + return nil } diff --git a/pkg/golinters/gci/internal/analyzer.go b/pkg/golinters/gci/internal/analyzer.go deleted file mode 100644 index 92921e48560e..000000000000 --- a/pkg/golinters/gci/internal/analyzer.go +++ /dev/null @@ -1,143 +0,0 @@ -package internal - -import ( - "go/token" - "strings" - - "github.com/daixiang0/gci/pkg/analyzer" - "github.com/daixiang0/gci/pkg/log" - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - - "github.com/daixiang0/gci/pkg/config" - "github.com/daixiang0/gci/pkg/gci" - "github.com/daixiang0/gci/pkg/io" -) - -const ( - NoInlineCommentsFlag = "noInlineComments" - NoPrefixCommentsFlag = "noPrefixComments" - SkipGeneratedFlag = "skipGenerated" - SectionsFlag = "Sections" - SectionSeparatorsFlag = "SectionSeparators" - NoLexOrderFlag = "NoLexOrder" - CustomOrderFlag = "CustomOrder" - PrefixDelimiterFlag = "PrefixDelimiter" -) - -const SectionDelimiter = "," - -var ( - noInlineComments bool - noPrefixComments bool - skipGenerated bool - sectionsStr string - sectionSeparatorsStr string - noLexOrder bool - customOrder bool - prefixDelimiter string -) - -func NewAnalyzer() *analysis.Analyzer { - log.InitLogger() - _ = log.L().Sync() - - a := &analysis.Analyzer{ - Name: "gci", - Doc: "Checks if code and import statements are formatted, it makes import statements always deterministic.", - Run: runAnalysis, - Requires: []*analysis.Analyzer{inspect.Analyzer}, - } - - a.Flags.BoolVar(&noInlineComments, NoInlineCommentsFlag, false, - "If comments in the same line as the input should be present") - a.Flags.BoolVar(&noPrefixComments, NoPrefixCommentsFlag, false, - "If comments above an input should be present") - a.Flags.BoolVar(&skipGenerated, SkipGeneratedFlag, false, - "Skip generated files") - a.Flags.StringVar(§ionsStr, SectionsFlag, "", - "Specify the Sections format that should be used to check the file formatting") - a.Flags.StringVar(§ionSeparatorsStr, SectionSeparatorsFlag, "", - "Specify the Sections that are inserted as Separators between Sections") - a.Flags.BoolVar(&noLexOrder, NoLexOrderFlag, false, - "Drops lexical ordering for custom sections") - a.Flags.BoolVar(&customOrder, CustomOrderFlag, false, - "Enable custom order of sections") - - a.Flags.StringVar(&prefixDelimiter, PrefixDelimiterFlag, SectionDelimiter, "") - - return a -} - -func runAnalysis(pass *analysis.Pass) (any, error) { - var fileReferences []*token.File - // extract file references for all files in the analyzer pass - for _, pkgFile := range pass.Files { - fileForPos := pass.Fset.File(pkgFile.Package) - if fileForPos != nil { - fileReferences = append(fileReferences, fileForPos) - } - } - - expectedNumFiles := len(pass.Files) - foundNumFiles := len(fileReferences) - if expectedNumFiles != foundNumFiles { - return nil, InvalidNumberOfFilesInAnalysis{expectedNumFiles, foundNumFiles} - } - - gciCfg, err := generateGciConfiguration(pass.Module.Path).Parse() - if err != nil { - return nil, err - } - - for _, file := range fileReferences { - unmodifiedFile, formattedFile, err := gci.LoadFormatGoFile(io.File{FilePath: file.Name()}, *gciCfg) - if err != nil { - return nil, err - } - - fix, err := analyzer.GetSuggestedFix(file, unmodifiedFile, formattedFile) - if err != nil { - return nil, err - } - - if fix == nil { - // no difference - continue - } - - pass.Report(analysis.Diagnostic{ - Pos: fix.TextEdits[0].Pos, - Message: "File is not properly formatted", - SuggestedFixes: []analysis.SuggestedFix{*fix}, - }) - } - - return nil, nil -} - -func generateGciConfiguration(modPath string) *config.YamlConfig { - fmtCfg := config.BoolConfig{ - NoInlineComments: noInlineComments, - NoPrefixComments: noPrefixComments, - Debug: false, - SkipGenerated: skipGenerated, - CustomOrder: customOrder, - NoLexOrder: noLexOrder, - } - - var sectionStrings []string - if sectionsStr != "" { - s := strings.Split(sectionsStr, SectionDelimiter) - for _, a := range s { - sectionStrings = append(sectionStrings, strings.ReplaceAll(a, prefixDelimiter, SectionDelimiter)) - } - } - - var sectionSeparatorStrings []string - if sectionSeparatorsStr != "" { - sectionSeparatorStrings = strings.Split(sectionSeparatorsStr, SectionDelimiter) - } - - return &config.YamlConfig{Cfg: fmtCfg, SectionStrings: sectionStrings, SectionSeparatorStrings: sectionSeparatorStrings, ModPath: modPath} -} diff --git a/pkg/golinters/gci/internal/errors.go b/pkg/golinters/gci/internal/errors.go deleted file mode 100644 index c9c0630ed731..000000000000 --- a/pkg/golinters/gci/internal/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package internal - -import "fmt" - -type InvalidNumberOfFilesInAnalysis struct { - expectedNumFiles, foundNumFiles int -} - -func (i InvalidNumberOfFilesInAnalysis) Error() string { - return fmt.Sprintf("Expected %d files in Analyzer input, Found %d", i.expectedNumFiles, i.foundNumFiles) -} diff --git a/pkg/golinters/gci/testdata/gci.go b/pkg/golinters/gci/testdata/gci.go index 27d533812250..f8cd3a21c50a 100644 --- a/pkg/golinters/gci/testdata/gci.go +++ b/pkg/golinters/gci/testdata/gci.go @@ -2,10 +2,9 @@ //golangcitest:config_path testdata/gci.yml package testdata -// want +1 "File is not properly formatted" -import ( +import ( // want "File is not properly formatted" "golang.org/x/tools/go/analysis" - "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/config" // want "File is not properly formatted" "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 c526c0b5753d..c82fc829f3e9 100644 --- a/pkg/golinters/gci/testdata/gci_cgo.go +++ b/pkg/golinters/gci/testdata/gci_cgo.go @@ -1,7 +1,3 @@ -//go:build ignore - -// TODO(ldez) the linter doesn't support cgo. - //golangcitest:args -Egci //golangcitest:config_path testdata/gci.yml package testdata @@ -16,10 +12,9 @@ package testdata */ import "C" -// want +1 "File is not properly formatted" -import ( +import ( // want "File is not properly formatted" "golang.org/x/tools/go/analysis" - "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/config" // want "File is not properly formatted" "unsafe" "fmt" "errors"