github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/golinters/gofumpt.go (about) 1 package golinters 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "sync" 9 10 "github.com/shazow/go-diff/difflib" 11 "golang.org/x/tools/go/analysis" 12 "mvdan.cc/gofumpt/format" 13 14 "github.com/chenfeining/golangci-lint/pkg/config" 15 "github.com/chenfeining/golangci-lint/pkg/golinters/goanalysis" 16 "github.com/chenfeining/golangci-lint/pkg/lint/linter" 17 ) 18 19 const gofumptName = "gofumpt" 20 21 type differ interface { 22 Diff(out io.Writer, a io.ReadSeeker, b io.ReadSeeker) error 23 } 24 25 func NewGofumpt(settings *config.GofumptSettings) *goanalysis.Linter { 26 var mu sync.Mutex 27 var resIssues []goanalysis.Issue 28 29 diff := difflib.New() 30 31 var options format.Options 32 33 if settings != nil { 34 options = format.Options{ 35 LangVersion: getLangVersion(settings), 36 ModulePath: settings.ModulePath, 37 ExtraRules: settings.ExtraRules, 38 } 39 } 40 41 analyzer := &analysis.Analyzer{ 42 Name: gofumptName, 43 Doc: goanalysis.TheOnlyanalyzerDoc, 44 Run: goanalysis.DummyRun, 45 } 46 47 return goanalysis.NewLinter( 48 gofumptName, 49 "Gofumpt checks whether code was gofumpt-ed.", 50 []*analysis.Analyzer{analyzer}, 51 nil, 52 ).WithContextSetter(func(lintCtx *linter.Context) { 53 analyzer.Run = func(pass *analysis.Pass) (any, error) { 54 issues, err := runGofumpt(lintCtx, pass, diff, options) 55 if err != nil { 56 return nil, err 57 } 58 59 if len(issues) == 0 { 60 return nil, nil 61 } 62 63 mu.Lock() 64 resIssues = append(resIssues, issues...) 65 mu.Unlock() 66 67 return nil, nil 68 } 69 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 70 return resIssues 71 }).WithLoadMode(goanalysis.LoadModeSyntax) 72 } 73 74 func runGofumpt(lintCtx *linter.Context, pass *analysis.Pass, diff differ, options format.Options) ([]goanalysis.Issue, error) { 75 fileNames := getFileNames(pass) 76 77 var issues []goanalysis.Issue 78 79 for _, f := range fileNames { 80 input, err := os.ReadFile(f) 81 if err != nil { 82 return nil, fmt.Errorf("unable to open file %s: %w", f, err) 83 } 84 85 output, err := format.Source(input, options) 86 if err != nil { 87 return nil, fmt.Errorf("error while running gofumpt: %w", err) 88 } 89 90 if !bytes.Equal(input, output) { 91 out := bytes.NewBufferString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", f)) 92 93 err := diff.Diff(out, bytes.NewReader(input), bytes.NewReader(output)) 94 if err != nil { 95 return nil, fmt.Errorf("error while running gofumpt: %w", err) 96 } 97 98 diff := out.String() 99 is, err := extractIssuesFromPatch(diff, lintCtx, gofumptName) 100 if err != nil { 101 return nil, fmt.Errorf("can't extract issues from gofumpt diff output %q: %w", diff, err) 102 } 103 104 for i := range is { 105 issues = append(issues, goanalysis.NewIssue(&is[i], pass)) 106 } 107 } 108 } 109 110 return issues, nil 111 } 112 113 func getLangVersion(settings *config.GofumptSettings) string { 114 if settings == nil || settings.LangVersion == "" { 115 // TODO: defaults to "1.15", in the future (v2) must be set by using build.Default.ReleaseTags like staticcheck. 116 return "1.15" 117 } 118 return settings.LangVersion 119 }