github.com/shulhan/golangci-lint@v1.10.1/pkg/golinters/gofmt.go (about) 1 package golinters 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "go/token" 8 9 gofmtAPI "github.com/golangci/gofmt/gofmt" 10 goimportsAPI "github.com/golangci/gofmt/goimports" 11 "github.com/golangci/golangci-lint/pkg/lint/linter" 12 "github.com/golangci/golangci-lint/pkg/logutils" 13 "github.com/golangci/golangci-lint/pkg/result" 14 "sourcegraph.com/sourcegraph/go-diff/diff" 15 ) 16 17 type Gofmt struct { 18 UseGoimports bool 19 } 20 21 func (g Gofmt) Name() string { 22 if g.UseGoimports { 23 return "goimports" 24 } 25 26 return "gofmt" 27 } 28 29 func (g Gofmt) Desc() string { 30 if g.UseGoimports { 31 return "Goimports does everything that gofmt does. Additionally it checks unused imports" 32 } 33 34 return "Gofmt checks whether code was gofmt-ed. By default " + 35 "this tool runs with -s option to check for code simplification" 36 } 37 38 func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) { 39 lines := bytes.Split(h.Body, []byte{'\n'}) 40 lineNumber := int(h.OrigStartLine - 1) 41 firstAddedLineNumber := -1 42 for _, line := range lines { 43 lineNumber++ 44 45 if len(line) == 0 { 46 continue 47 } 48 if line[0] == '+' && firstAddedLineNumber == -1 { 49 firstAddedLineNumber = lineNumber 50 } 51 if line[0] == '-' { 52 return lineNumber, firstAddedLineNumber, nil 53 } 54 } 55 56 return 0, firstAddedLineNumber, fmt.Errorf("didn't find deletion line in hunk %s", string(h.Body)) 57 } 58 59 func (g Gofmt) extractIssuesFromPatch(patch string, log logutils.Log) ([]result.Issue, error) { 60 diffs, err := diff.ParseMultiFileDiff([]byte(patch)) 61 if err != nil { 62 return nil, fmt.Errorf("can't parse patch: %s", err) 63 } 64 65 if len(diffs) == 0 { 66 return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs) 67 } 68 69 issues := []result.Issue{} 70 for _, d := range diffs { 71 if len(d.Hunks) == 0 { 72 log.Warnf("Got no hunks in diff %+v", d) 73 continue 74 } 75 76 for _, hunk := range d.Hunks { 77 deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk) 78 if err != nil { 79 if addedLine > 1 { 80 deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines 81 } else { 82 deletedLine = 1 83 } 84 } 85 86 text := "File is not `gofmt`-ed with `-s`" 87 if g.UseGoimports { 88 text = "File is not `goimports`-ed" 89 } 90 i := result.Issue{ 91 FromLinter: g.Name(), 92 Pos: token.Position{ 93 Filename: d.NewName, 94 Line: deletedLine, 95 }, 96 Text: text, 97 } 98 issues = append(issues, i) 99 } 100 } 101 102 return issues, nil 103 } 104 105 func (g Gofmt) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { 106 var issues []result.Issue 107 108 for _, f := range lintCtx.PkgProgram.Files(lintCtx.Cfg.Run.AnalyzeTests) { 109 var diff []byte 110 var err error 111 if g.UseGoimports { 112 diff, err = goimportsAPI.Run(f) 113 } else { 114 diff, err = gofmtAPI.Run(f, lintCtx.Settings().Gofmt.Simplify) 115 } 116 if err != nil { // TODO: skip 117 return nil, err 118 } 119 if diff == nil { 120 continue 121 } 122 123 is, err := g.extractIssuesFromPatch(string(diff), lintCtx.Log) 124 if err != nil { 125 return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err) 126 } 127 128 issues = append(issues, is...) 129 } 130 131 return issues, nil 132 }