174 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
						|
}
 |