259 lines
5.3 KiB
Go
259 lines
5.3 KiB
Go
package processors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"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're achieving this
|
|
// by sorting results.Issues using processor step, and chain based
|
|
// rules that can compare different properties of the Issues struct.
|
|
|
|
const (
|
|
orderNameFile = "file"
|
|
orderNameLinter = "linter"
|
|
orderNameSeverity = "severity"
|
|
)
|
|
|
|
var _ Processor = (*SortResults)(nil)
|
|
|
|
type SortResults struct {
|
|
cmps map[string]*comparator
|
|
|
|
cfg *config.Output
|
|
}
|
|
|
|
func NewSortResults(cfg *config.Config) *SortResults {
|
|
return &SortResults{
|
|
cmps: map[string]*comparator{
|
|
// For sorting we are comparing (in next order):
|
|
// file names, line numbers, position, and finally - giving up.
|
|
orderNameFile: byFileName().SetNext(byLine().SetNext(byColumn())),
|
|
// For sorting we are comparing: linter name
|
|
orderNameLinter: byLinter(),
|
|
// For sorting we are comparing: severity
|
|
orderNameSeverity: bySeverity(),
|
|
},
|
|
cfg: &cfg.Output,
|
|
}
|
|
}
|
|
|
|
// Process is performing sorting of the result issues.
|
|
func (sr SortResults) Process(issues []result.Issue) ([]result.Issue, error) {
|
|
if !sr.cfg.SortResults {
|
|
return issues, nil
|
|
}
|
|
|
|
if len(sr.cfg.SortOrder) == 0 {
|
|
sr.cfg.SortOrder = []string{orderNameFile}
|
|
}
|
|
|
|
var cmps []*comparator
|
|
for _, name := range sr.cfg.SortOrder {
|
|
if c, ok := sr.cmps[name]; ok {
|
|
cmps = append(cmps, c)
|
|
} else {
|
|
return nil, fmt.Errorf("unsupported sort-order name %q", name)
|
|
}
|
|
}
|
|
|
|
cmp, err := mergeComparators(cmps)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sort.Slice(issues, func(i, j int) bool {
|
|
return 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
|
|
}
|
|
|
|
func (c compareResult) String() string {
|
|
switch c {
|
|
case less:
|
|
return "less"
|
|
case equal:
|
|
return "equal"
|
|
case greater:
|
|
return "greater"
|
|
default:
|
|
return "none"
|
|
}
|
|
}
|
|
|
|
// comparator describes how to implement compare for two "issues".
|
|
type comparator struct {
|
|
name string
|
|
compare func(a, b *result.Issue) compareResult
|
|
next *comparator
|
|
}
|
|
|
|
func (cmp *comparator) Next() *comparator { return cmp.next }
|
|
|
|
func (cmp *comparator) SetNext(c *comparator) *comparator {
|
|
cmp.next = c
|
|
return cmp
|
|
}
|
|
|
|
func (cmp *comparator) String() string {
|
|
s := cmp.name
|
|
if cmp.Next() != nil {
|
|
s += " > " + cmp.Next().String()
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (cmp *comparator) Compare(a, b *result.Issue) compareResult {
|
|
res := cmp.compare(a, b)
|
|
if !res.isNeutral() {
|
|
return res
|
|
}
|
|
|
|
if next := cmp.Next(); next != nil {
|
|
return next.Compare(a, b)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func byFileName() *comparator {
|
|
return &comparator{
|
|
name: "byFileName",
|
|
compare: func(a, b *result.Issue) compareResult {
|
|
return compareResult(strings.Compare(a.FilePath(), b.FilePath()))
|
|
},
|
|
}
|
|
}
|
|
|
|
func byLine() *comparator {
|
|
return &comparator{
|
|
name: "byLine",
|
|
compare: func(a, b *result.Issue) compareResult {
|
|
return numericCompare(a.Line(), b.Line())
|
|
},
|
|
}
|
|
}
|
|
|
|
func byColumn() *comparator {
|
|
return &comparator{
|
|
name: "byColumn",
|
|
compare: func(a, b *result.Issue) compareResult {
|
|
return numericCompare(a.Column(), b.Column())
|
|
},
|
|
}
|
|
}
|
|
|
|
func byLinter() *comparator {
|
|
return &comparator{
|
|
name: "byLinter",
|
|
compare: func(a, b *result.Issue) compareResult {
|
|
return compareResult(strings.Compare(a.FromLinter, b.FromLinter))
|
|
},
|
|
}
|
|
}
|
|
|
|
func bySeverity() *comparator {
|
|
return &comparator{
|
|
name: "bySeverity",
|
|
compare: func(a, b *result.Issue) compareResult {
|
|
return severityCompare(a.Severity, b.Severity)
|
|
},
|
|
}
|
|
}
|
|
|
|
func mergeComparators(cmps []*comparator) (*comparator, error) {
|
|
if len(cmps) == 0 {
|
|
return nil, errors.New("no comparator")
|
|
}
|
|
|
|
for i := 0; i < len(cmps)-1; i++ {
|
|
findComparatorTip(cmps[i]).SetNext(cmps[i+1])
|
|
}
|
|
|
|
return cmps[0], nil
|
|
}
|
|
|
|
func findComparatorTip(cmp *comparator) *comparator {
|
|
if cmp.Next() != nil {
|
|
return findComparatorTip(cmp.Next())
|
|
}
|
|
|
|
return cmp
|
|
}
|
|
|
|
func severityCompare(a, b string) compareResult {
|
|
// The position inside the slice define the importance (lower to higher).
|
|
classic := []string{"low", "medium", "high", "warning", "error"}
|
|
|
|
if slices.Contains(classic, a) && slices.Contains(classic, b) {
|
|
switch {
|
|
case slices.Index(classic, a) > slices.Index(classic, b):
|
|
return greater
|
|
case slices.Index(classic, a) < slices.Index(classic, b):
|
|
return less
|
|
default:
|
|
return equal
|
|
}
|
|
}
|
|
|
|
if slices.Contains(classic, a) {
|
|
return greater
|
|
}
|
|
|
|
if slices.Contains(classic, b) {
|
|
return less
|
|
}
|
|
|
|
return compareResult(strings.Compare(a, b))
|
|
}
|
|
|
|
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
|
|
}
|