158 lines
4.1 KiB
Go
158 lines
4.1 KiB
Go
package golinters
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
gcicfg "github.com/daixiang0/gci/pkg/config"
|
|
"github.com/daixiang0/gci/pkg/gci"
|
|
"github.com/daixiang0/gci/pkg/io"
|
|
"github.com/daixiang0/gci/pkg/log"
|
|
"github.com/hexops/gotextdiff"
|
|
"github.com/hexops/gotextdiff/myers"
|
|
"github.com/hexops/gotextdiff/span"
|
|
"golang.org/x/tools/go/analysis"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/config"
|
|
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
|
)
|
|
|
|
const gciName = "gci"
|
|
|
|
func NewGci(settings *config.GciSettings) *goanalysis.Linter {
|
|
var mu sync.Mutex
|
|
var resIssues []goanalysis.Issue
|
|
|
|
analyzer := &analysis.Analyzer{
|
|
Name: gciName,
|
|
Doc: goanalysis.TheOnlyanalyzerDoc,
|
|
Run: goanalysis.DummyRun,
|
|
}
|
|
|
|
var cfg *gcicfg.Config
|
|
if settings != nil {
|
|
rawCfg := gcicfg.YamlConfig{
|
|
Cfg: gcicfg.BoolConfig{
|
|
SkipGenerated: settings.SkipGenerated,
|
|
CustomOrder: settings.CustomOrder,
|
|
},
|
|
SectionStrings: settings.Sections,
|
|
}
|
|
|
|
if settings.LocalPrefixes != "" {
|
|
prefix := []string{"standard", "default", fmt.Sprintf("prefix(%s)", settings.LocalPrefixes)}
|
|
rawCfg.SectionStrings = prefix
|
|
}
|
|
|
|
var err error
|
|
cfg, err = rawCfg.Parse()
|
|
if err != nil {
|
|
linterLogger.Fatalf("gci: configuration parsing: %v", err)
|
|
}
|
|
}
|
|
|
|
var lock sync.Mutex
|
|
|
|
return goanalysis.NewLinter(
|
|
gciName,
|
|
"Gci controls Go package import order and makes it always deterministic.",
|
|
[]*analysis.Analyzer{analyzer},
|
|
nil,
|
|
).WithContextSetter(func(lintCtx *linter.Context) {
|
|
analyzer.Run = func(pass *analysis.Pass) (any, error) {
|
|
issues, err := runGci(pass, lintCtx, cfg, &lock)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(issues) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
mu.Lock()
|
|
resIssues = append(resIssues, issues...)
|
|
mu.Unlock()
|
|
|
|
return nil, nil
|
|
}
|
|
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
|
|
return resIssues
|
|
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
|
}
|
|
|
|
func runGci(pass *analysis.Pass, lintCtx *linter.Context, cfg *gcicfg.Config, lock *sync.Mutex) ([]goanalysis.Issue, error) {
|
|
fileNames := getFileNames(pass)
|
|
|
|
var diffs []string
|
|
err := diffFormattedFilesToArray(fileNames, *cfg, &diffs, lock)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var issues []goanalysis.Issue
|
|
|
|
for _, diff := range diffs {
|
|
if diff == "" {
|
|
continue
|
|
}
|
|
|
|
is, err := extractIssuesFromPatch(diff, lintCtx, gciName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't extract issues from gci diff output %s: %w", diff, err)
|
|
}
|
|
|
|
for i := range is {
|
|
issues = append(issues, goanalysis.NewIssue(&is[i], pass))
|
|
}
|
|
}
|
|
|
|
return issues, nil
|
|
}
|
|
|
|
// diffFormattedFilesToArray is a copy of gci.DiffFormattedFilesToArray without io.StdInGenerator.
|
|
// gci.DiffFormattedFilesToArray uses gci.processStdInAndGoFilesInPaths that uses io.StdInGenerator but stdin is not active on CI.
|
|
// https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L63-L75
|
|
// https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L80
|
|
func diffFormattedFilesToArray(paths []string, cfg gcicfg.Config, diffs *[]string, lock *sync.Mutex) error {
|
|
log.InitLogger()
|
|
defer func() { _ = log.L().Sync() }()
|
|
|
|
return gci.ProcessFiles(io.GoFilesInPathsGenerator(paths, true), cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
|
|
fileURI := span.URIFromPath(filePath)
|
|
edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile))
|
|
unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits)
|
|
lock.Lock()
|
|
*diffs = append(*diffs, fmt.Sprint(unifiedEdits))
|
|
lock.Unlock()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func getErrorTextForGci(settings config.GciSettings) string {
|
|
text := "File is not `gci`-ed"
|
|
|
|
hasOptions := settings.SkipGenerated || len(settings.Sections) > 0
|
|
if !hasOptions {
|
|
return text
|
|
}
|
|
|
|
text += " with"
|
|
|
|
if settings.SkipGenerated {
|
|
text += " --skip-generated"
|
|
}
|
|
|
|
if len(settings.Sections) > 0 {
|
|
for _, section := range settings.Sections {
|
|
text += " -s " + section
|
|
}
|
|
}
|
|
|
|
if settings.CustomOrder {
|
|
text += " --custom-order"
|
|
}
|
|
|
|
return text
|
|
}
|