package golinters import ( "bytes" "context" "fmt" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-shared/pkg/analytics" "github.com/golangci/golangci-shared/pkg/executors" "sourcegraph.com/sourcegraph/go-diff/diff" ) type gofmt struct { useGoimports bool } func (g gofmt) Name() string { if g.useGoimports { return "goimports" } return "gofmt" } func getFirstDeletedLineNumberInHunk(h *diff.Hunk) (int, error) { lines := bytes.Split(h.Body, []byte{'\n'}) lineNumber := int(h.OrigStartLine - 1) for _, line := range lines { lineNumber++ if len(line) == 0 { continue } if line[0] == '-' { return lineNumber, nil } } return 0, fmt.Errorf("didn't find deletion line in hunk %s", string(h.Body)) } func (g gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) { diffs, err := diff.ParseMultiFileDiff([]byte(patch)) if err != nil { return nil, fmt.Errorf("can't parse patch: %s", err) } if len(diffs) == 0 { return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs) } issues := []result.Issue{} for _, d := range diffs { if len(d.Hunks) == 0 { analytics.Log(context.TODO()).Warnf("Got no hunks in diff %+v", d) continue } for _, hunk := range d.Hunks { lineNumber, err := getFirstDeletedLineNumberInHunk(hunk) if err != nil { analytics.Log(context.TODO()).Infof("Can't get first deleted line number for hunk: %s", err) lineNumber = int(hunk.OrigStartLine) // use first line if no deletions: } text := "File is not gofmt-ed with -s" if g.useGoimports { text = "File is not goimports-ed" } i := result.Issue{ FromLinter: g.Name(), File: d.NewName, LineNumber: lineNumber, Text: text, } issues = append(issues, i) } } return issues, nil } func (g gofmt) Run(ctx context.Context, exec executors.Executor, cfg *config.Run) (*result.Result, error) { // TODO: cfg support paths, err := getPathsForGoProject(exec.WorkDir()) if err != nil { return nil, fmt.Errorf("can't get files to analyze: %s", err) } args := []string{"-d"} if !g.useGoimports { args = append(args, "-s") } args = append(args, paths.files...) out, err := exec.Run(ctx, g.Name(), args...) if err != nil { return nil, fmt.Errorf("can't run gofmt: %s, %s", err, out) } if len(out) == 0 { // no diff => no issues return &result.Result{ Issues: []result.Issue{}, }, nil } issues, err := g.extractIssuesFromPatch(out) if err != nil { return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", out, err) } return &result.Result{ Issues: issues, MaxIssuesPerFile: 1, // don't disturb user: show just first changed not gofmt-ed line }, nil }