174 lines
3.4 KiB
Go

package processors
import (
"sort"
"strings"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/result"
)
// Base propose of this functionality to sort results (issues)
// produced by various linters by analyzing code. We achieving this
// by sorting results.Issues using processor step, and chain based
// rules that can compare different properties of the Issues struct.
var _ Processor = (*SortResults)(nil)
type SortResults struct {
cmp comparator
cfg *config.Config
}
func NewSortResults(cfg *config.Config) *SortResults {
// For sorting we are comparing (in next order): file names, line numbers,
// position, and finally - giving up.
return &SortResults{
cmp: ByName{
next: ByLine{
next: ByColumn{},
},
},
cfg: cfg,
}
}
// Process is performing sorting of the result issues.
func (sr SortResults) Process(issues []result.Issue) ([]result.Issue, error) {
if !sr.cfg.Output.SortResults {
return issues, nil
}
sort.Slice(issues, func(i, j int) bool {
return sr.cmp.Compare(&issues[i], &issues[j]) == Less
})
return issues, nil
}
func (sr SortResults) Name() string { return "sort_results" }
func (sr SortResults) Finish() {}
type compareResult int
const (
Less compareResult = iota - 1
Equal
Greater
None
)
func (c compareResult) isNeutral() bool {
// return true if compare result is incomparable or equal.
return c == None || c == Equal
}
//nolint:exhaustive
func (c compareResult) String() string {
switch c {
case Less:
return "Less"
case Equal:
return "Equal"
case Greater:
return "Greater"
}
return "None"
}
// comparator describe how to implement compare for two "issues" lexicographically
type comparator interface {
Compare(a, b *result.Issue) compareResult
Next() comparator
}
var (
_ comparator = (*ByName)(nil)
_ comparator = (*ByLine)(nil)
_ comparator = (*ByColumn)(nil)
)
type ByName struct{ next comparator }
//nolint:golint
func (cmp ByName) Next() comparator { return cmp.next }
//nolint:golint
func (cmp ByName) Compare(a, b *result.Issue) compareResult {
var res compareResult
if res = compareResult(strings.Compare(a.FilePath(), b.FilePath())); !res.isNeutral() {
return res
}
if next := cmp.Next(); next != nil {
return next.Compare(a, b)
}
return res
}
type ByLine struct{ next comparator }
//nolint:golint
func (cmp ByLine) Next() comparator { return cmp.next }
//nolint:golint
func (cmp ByLine) Compare(a, b *result.Issue) compareResult {
var res compareResult
if res = numericCompare(a.Line(), b.Line()); !res.isNeutral() {
return res
}
if next := cmp.Next(); next != nil {
return next.Compare(a, b)
}
return res
}
type ByColumn struct{ next comparator }
//nolint:golint
func (cmp ByColumn) Next() comparator { return cmp.next }
//nolint:golint
func (cmp ByColumn) Compare(a, b *result.Issue) compareResult {
var res compareResult
if res = numericCompare(a.Column(), b.Column()); !res.isNeutral() {
return res
}
if next := cmp.Next(); next != nil {
return next.Compare(a, b)
}
return res
}
func numericCompare(a, b int) compareResult {
var (
isValuesInvalid = a < 0 || b < 0
isZeroValuesBoth = a == 0 && b == 0
isEqual = a == b
isZeroValueInA = b > 0 && a == 0
isZeroValueInB = a > 0 && b == 0
)
switch {
case isZeroValuesBoth || isEqual:
return Equal
case isValuesInvalid || isZeroValueInA || isZeroValueInB:
return None
case a > b:
return Greater
case a < b:
return Less
}
return Equal
}