129 lines
3.2 KiB
Go
129 lines
3.2 KiB
Go
|
|
package s1032
|
||
|
|
|
||
|
|
import (
|
||
|
|
"go/ast"
|
||
|
|
"go/token"
|
||
|
|
"sort"
|
||
|
|
|
||
|
|
"honnef.co/go/tools/analysis/code"
|
||
|
|
"honnef.co/go/tools/analysis/facts/generated"
|
||
|
|
"honnef.co/go/tools/analysis/lint"
|
||
|
|
"honnef.co/go/tools/analysis/report"
|
||
|
|
"honnef.co/go/tools/knowledge"
|
||
|
|
|
||
|
|
"golang.org/x/tools/go/analysis"
|
||
|
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
||
|
|
)
|
||
|
|
|
||
|
|
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||
|
|
Analyzer: &analysis.Analyzer{
|
||
|
|
Name: "S1032",
|
||
|
|
Run: run,
|
||
|
|
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
|
||
|
|
},
|
||
|
|
Doc: &lint.RawDocumentation{
|
||
|
|
Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`,
|
||
|
|
Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to
|
||
|
|
read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\'
|
||
|
|
and \'sort.Sort(sort.StringSlice(x))\'.`,
|
||
|
|
Before: `sort.Sort(sort.StringSlice(x))`,
|
||
|
|
After: `sort.Strings(x)`,
|
||
|
|
Since: "2019.1",
|
||
|
|
MergeIf: lint.MergeIfAny,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
var Analyzer = SCAnalyzer.Analyzer
|
||
|
|
|
||
|
|
func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool {
|
||
|
|
call := node.(*ast.CallExpr)
|
||
|
|
typeconv, ok := call.Args[0].(*ast.CallExpr)
|
||
|
|
if !ok {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
sel, ok := typeconv.Fun.(*ast.SelectorExpr)
|
||
|
|
if !ok {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
name := code.SelectorName(pass, sel)
|
||
|
|
switch name {
|
||
|
|
case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice":
|
||
|
|
default:
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func run(pass *analysis.Pass) (any, error) {
|
||
|
|
type Error struct {
|
||
|
|
node ast.Node
|
||
|
|
msg string
|
||
|
|
}
|
||
|
|
var allErrors []Error
|
||
|
|
fn := func(node ast.Node) {
|
||
|
|
var body *ast.BlockStmt
|
||
|
|
switch node := node.(type) {
|
||
|
|
case *ast.FuncLit:
|
||
|
|
body = node.Body
|
||
|
|
case *ast.FuncDecl:
|
||
|
|
body = node.Body
|
||
|
|
default:
|
||
|
|
lint.ExhaustiveTypeSwitch(node)
|
||
|
|
}
|
||
|
|
if body == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var errors []Error
|
||
|
|
permissible := false
|
||
|
|
fnSorts := func(node ast.Node) bool {
|
||
|
|
if permissible {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if !code.IsCallTo(pass, node, "sort.Sort") {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
if isPermissibleSort(pass, node) {
|
||
|
|
permissible = true
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
call := node.(*ast.CallExpr)
|
||
|
|
// isPermissibleSort guarantees that this type assertion will succeed
|
||
|
|
typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr)
|
||
|
|
sel := typeconv.Fun.(*ast.SelectorExpr)
|
||
|
|
name := code.SelectorName(pass, sel)
|
||
|
|
|
||
|
|
switch name {
|
||
|
|
case "sort.IntSlice":
|
||
|
|
errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"})
|
||
|
|
case "sort.Float64Slice":
|
||
|
|
errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"})
|
||
|
|
case "sort.StringSlice":
|
||
|
|
errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"})
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
ast.Inspect(body, fnSorts)
|
||
|
|
|
||
|
|
if permissible {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
allErrors = append(allErrors, errors...)
|
||
|
|
}
|
||
|
|
code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil))
|
||
|
|
sort.Slice(allErrors, func(i, j int) bool {
|
||
|
|
return allErrors[i].node.Pos() < allErrors[j].node.Pos()
|
||
|
|
})
|
||
|
|
var prev token.Pos
|
||
|
|
for _, err := range allErrors {
|
||
|
|
if err.node.Pos() == prev {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
prev = err.node.Pos()
|
||
|
|
report.Report(pass, err.node, err.msg, report.FilterGenerated())
|
||
|
|
}
|
||
|
|
return nil, nil
|
||
|
|
}
|