Isaev Denis 95ec0cf21e
dramatically reduce memory usage ()
Run all linters per package. It allows unloading package data when it's
processed. It dramatically reduces memory (and CPU because of GC) usage.

Relates: 
2019-09-30 16:19:41 +03:00

268 lines
6.3 KiB
Go

package golinters
import (
"bytes"
"fmt"
"go/token"
"strings"
"github.com/pkg/errors"
diffpkg "github.com/sourcegraph/go-diff/diff"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result"
)
type Change struct {
LineRange result.Range
Replacement result.Replacement
}
type diffLineType string
const (
diffLineAdded diffLineType = "added"
diffLineOriginal diffLineType = "original"
diffLineDeleted diffLineType = "deleted"
)
type diffLine struct {
originalNumber int // 1-based original line number
typ diffLineType
data string // "+" or "-" stripped line
}
type hunkChangesParser struct {
// needed because we merge currently added lines with the last original line
lastOriginalLine *diffLine
// if the first line of diff is an adding we save all additions to replacementLinesToPrepend
replacementLinesToPrepend []string
log logutils.Log
lines []diffLine
ret []Change
}
func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
lines := bytes.Split(h.Body, []byte{'\n'})
currentOriginalLineNumer := int(h.OrigStartLine)
var ret []diffLine
for i, line := range lines {
dl := diffLine{
originalNumber: currentOriginalLineNumer,
}
lineStr := string(line)
//nolint:gocritic
if strings.HasPrefix(lineStr, "-") {
dl.typ = diffLineDeleted
dl.data = strings.TrimPrefix(lineStr, "-")
currentOriginalLineNumer++
} else if strings.HasPrefix(lineStr, "+") {
dl.typ = diffLineAdded
dl.data = strings.TrimPrefix(lineStr, "+")
} else {
if i == len(lines)-1 && lineStr == "" {
// handle last \n: don't add an empty original line
break
}
dl.typ = diffLineOriginal
dl.data = strings.TrimPrefix(lineStr, " ")
currentOriginalLineNumer++
}
ret = append(ret, dl)
}
p.lines = ret
}
func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
if len(p.replacementLinesToPrepend) == 0 {
p.lastOriginalLine = &line
*i++
return
}
// check following added lines for the case:
// + added line 1
// original line
// + added line 2
*i++
var followingAddedLines []string
for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
followingAddedLines = append(followingAddedLines, p.lines[*i].data)
}
p.ret = append(p.ret, Change{
LineRange: result.Range{
From: line.originalNumber,
To: line.originalNumber,
},
Replacement: result.Replacement{
NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
},
})
p.replacementLinesToPrepend = nil
p.lastOriginalLine = &line
}
func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
change := Change{
LineRange: result.Range{
From: deletedLines[0].originalNumber,
To: deletedLines[len(deletedLines)-1].originalNumber,
},
}
if len(addedLines) != 0 {
//nolint:gocritic
change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...)
if len(p.replacementLinesToPrepend) != 0 {
p.replacementLinesToPrepend = nil
}
p.ret = append(p.ret, change)
return
}
// delete-only change with possible prepending
if len(p.replacementLinesToPrepend) != 0 {
change.Replacement.NewLines = p.replacementLinesToPrepend
p.replacementLinesToPrepend = nil
} else {
change.Replacement.NeedOnlyDelete = true
}
p.ret = append(p.ret, change)
}
func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
if p.lastOriginalLine == nil {
// the first line is added; the diff looks like:
// 1. + ...
// 2. - ...
// or
// 1. + ...
// 2. ...
p.replacementLinesToPrepend = addedLines
return
}
// add-only change merged into the last original line with possible prepending
p.ret = append(p.ret, Change{
LineRange: result.Range{
From: p.lastOriginalLine.originalNumber,
To: p.lastOriginalLine.originalNumber,
},
Replacement: result.Replacement{
NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
},
})
p.replacementLinesToPrepend = nil
}
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
p.parseDiffLines(h)
for i := 0; i < len(p.lines); {
line := p.lines[i]
if line.typ == diffLineOriginal {
p.handleOriginalLine(line, &i) //nolint:scopelint
continue
}
var deletedLines []diffLine
for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
deletedLines = append(deletedLines, p.lines[i])
}
var addedLines []string
for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
addedLines = append(addedLines, p.lines[i].data)
}
if len(deletedLines) != 0 {
p.handleDeletedLines(deletedLines, addedLines)
continue
}
// no deletions, only addings
p.handleAddedOnlyLines(addedLines)
}
if len(p.replacementLinesToPrepend) != 0 {
p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
return nil
}
return p.ret
}
func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, isGoimports bool) ([]result.Issue, error) {
diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
if err != nil {
return nil, errors.Wrap(err, "can't parse patch")
}
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 {
log.Warnf("Got no hunks in diff %+v", d)
continue
}
for _, hunk := range d.Hunks {
var text string
if isGoimports {
text = "File is not `goimports`-ed"
} else {
text = "File is not `gofmt`-ed"
if lintCtx.Settings().Gofmt.Simplify {
text += " with `-s`"
}
}
p := hunkChangesParser{
log: log,
}
changes := p.parse(hunk)
for _, change := range changes {
change := change // fix scope
linterName := gofmtName
if isGoimports {
linterName = goimportsName
}
i := result.Issue{
FromLinter: linterName,
Pos: token.Position{
Filename: d.NewName,
Line: change.LineRange.From,
},
Text: text,
Replacement: &change.Replacement,
}
if change.LineRange.From != change.LineRange.To {
i.LineRange = &change.LineRange
}
issues = append(issues, i)
}
}
}
return issues, nil
}