github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/golinters/gci.go (about) 1 package golinters 2 3 import ( 4 "fmt" 5 "sync" 6 7 gcicfg "github.com/daixiang0/gci/pkg/config" 8 "github.com/daixiang0/gci/pkg/gci" 9 "github.com/daixiang0/gci/pkg/io" 10 "github.com/daixiang0/gci/pkg/log" 11 "github.com/hexops/gotextdiff" 12 "github.com/hexops/gotextdiff/myers" 13 "github.com/hexops/gotextdiff/span" 14 "golang.org/x/tools/go/analysis" 15 16 "github.com/chenfeining/golangci-lint/pkg/config" 17 "github.com/chenfeining/golangci-lint/pkg/golinters/goanalysis" 18 "github.com/chenfeining/golangci-lint/pkg/lint/linter" 19 ) 20 21 const gciName = "gci" 22 23 func NewGci(settings *config.GciSettings) *goanalysis.Linter { 24 var mu sync.Mutex 25 var resIssues []goanalysis.Issue 26 27 analyzer := &analysis.Analyzer{ 28 Name: gciName, 29 Doc: goanalysis.TheOnlyanalyzerDoc, 30 Run: goanalysis.DummyRun, 31 } 32 33 var cfg *gcicfg.Config 34 if settings != nil { 35 rawCfg := gcicfg.YamlConfig{ 36 Cfg: gcicfg.BoolConfig{ 37 SkipGenerated: settings.SkipGenerated, 38 CustomOrder: settings.CustomOrder, 39 }, 40 SectionStrings: settings.Sections, 41 } 42 43 if settings.LocalPrefixes != "" { 44 prefix := []string{"standard", "default", fmt.Sprintf("prefix(%s)", settings.LocalPrefixes)} 45 rawCfg.SectionStrings = prefix 46 } 47 48 var err error 49 cfg, err = rawCfg.Parse() 50 if err != nil { 51 linterLogger.Fatalf("gci: configuration parsing: %v", err) 52 } 53 } 54 55 var lock sync.Mutex 56 57 return goanalysis.NewLinter( 58 gciName, 59 "Gci controls Go package import order and makes it always deterministic.", 60 []*analysis.Analyzer{analyzer}, 61 nil, 62 ).WithContextSetter(func(lintCtx *linter.Context) { 63 analyzer.Run = func(pass *analysis.Pass) (any, error) { 64 issues, err := runGci(pass, lintCtx, cfg, &lock) 65 if err != nil { 66 return nil, err 67 } 68 69 if len(issues) == 0 { 70 return nil, nil 71 } 72 73 mu.Lock() 74 resIssues = append(resIssues, issues...) 75 mu.Unlock() 76 77 return nil, nil 78 } 79 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 80 return resIssues 81 }).WithLoadMode(goanalysis.LoadModeSyntax) 82 } 83 84 func runGci(pass *analysis.Pass, lintCtx *linter.Context, cfg *gcicfg.Config, lock *sync.Mutex) ([]goanalysis.Issue, error) { 85 fileNames := getFileNames(pass) 86 87 var diffs []string 88 err := diffFormattedFilesToArray(fileNames, *cfg, &diffs, lock) 89 if err != nil { 90 return nil, err 91 } 92 93 var issues []goanalysis.Issue 94 95 for _, diff := range diffs { 96 if diff == "" { 97 continue 98 } 99 100 is, err := extractIssuesFromPatch(diff, lintCtx, gciName) 101 if err != nil { 102 return nil, fmt.Errorf("can't extract issues from gci diff output %s: %w", diff, err) 103 } 104 105 for i := range is { 106 issues = append(issues, goanalysis.NewIssue(&is[i], pass)) 107 } 108 } 109 110 return issues, nil 111 } 112 113 // diffFormattedFilesToArray is a copy of gci.DiffFormattedFilesToArray without io.StdInGenerator. 114 // gci.DiffFormattedFilesToArray uses gci.processStdInAndGoFilesInPaths that uses io.StdInGenerator but stdin is not active on CI. 115 // https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L63-L75 116 // https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L80 117 func diffFormattedFilesToArray(paths []string, cfg gcicfg.Config, diffs *[]string, lock *sync.Mutex) error { 118 log.InitLogger() 119 defer func() { _ = log.L().Sync() }() 120 121 return gci.ProcessFiles(io.GoFilesInPathsGenerator(paths), cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { 122 fileURI := span.URIFromPath(filePath) 123 edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile)) 124 unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits) 125 lock.Lock() 126 *diffs = append(*diffs, fmt.Sprint(unifiedEdits)) 127 lock.Unlock() 128 return nil 129 }) 130 } 131 132 func getErrorTextForGci(settings config.GciSettings) string { 133 text := "File is not `gci`-ed" 134 135 hasOptions := settings.SkipGenerated || len(settings.Sections) > 0 136 if !hasOptions { 137 return text 138 } 139 140 text += " with" 141 142 if settings.SkipGenerated { 143 text += " --skip-generated" 144 } 145 146 if len(settings.Sections) > 0 { 147 for _, section := range settings.Sections { 148 text += " -s " + section 149 } 150 } 151 152 if settings.CustomOrder { 153 text += " --custom-order" 154 } 155 156 return text 157 }