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