2018-05-06 09:41:48 +03:00

126 lines
3.0 KiB
Go

package golinters
import (
"bytes"
"context"
"fmt"
gofmtAPI "github.com/golangci/gofmt/gofmt"
goimportsAPI "github.com/golangci/gofmt/goimports"
"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 getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) {
lines := bytes.Split(h.Body, []byte{'\n'})
lineNumber := int(h.OrigStartLine - 1)
firstAddedLineNumber := -1
for _, line := range lines {
lineNumber++
if len(line) == 0 {
continue
}
if line[0] == '+' && firstAddedLineNumber == -1 {
firstAddedLineNumber = lineNumber
}
if line[0] == '-' {
return lineNumber, firstAddedLineNumber, nil
}
}
return 0, firstAddedLineNumber, 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 {
deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk)
if err != nil {
analytics.Log(context.TODO()).Infof("Can't get first deleted line number for hunk: %s", err)
if addedLine > 1 {
deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines
} else {
deletedLine = 1
}
}
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: deletedLine,
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) {
var issues []result.Issue
for _, f := range cfg.Paths.Files {
var diff []byte
var err error
if g.useGoimports {
diff, err = goimportsAPI.Run(f)
} else {
diff, err = gofmtAPI.Run(f, cfg.Gofmt.Simplify)
}
if err != nil { // TODO: skip
return nil, err
}
if diff == nil {
continue
}
is, err := g.extractIssuesFromPatch(string(diff))
if err != nil {
return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err)
}
issues = append(issues, is...)
}
return &result.Result{
Issues: issues,
MaxIssuesPerFile: 1, // don't disturb user: show just first changed not gofmt-ed line
}, nil
}