s1032.go raw
1 package s1032
2
3 import (
4 "go/ast"
5 "go/token"
6 "sort"
7
8 "honnef.co/go/tools/analysis/code"
9 "honnef.co/go/tools/analysis/facts/generated"
10 "honnef.co/go/tools/analysis/lint"
11 "honnef.co/go/tools/analysis/report"
12 "honnef.co/go/tools/knowledge"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 )
17
18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
19 Analyzer: &analysis.Analyzer{
20 Name: "S1032",
21 Run: run,
22 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
23 },
24 Doc: &lint.RawDocumentation{
25 Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`,
26 Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to
27 read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\'
28 and \'sort.Sort(sort.StringSlice(x))\'.`,
29 Before: `sort.Sort(sort.StringSlice(x))`,
30 After: `sort.Strings(x)`,
31 Since: "2019.1",
32 MergeIf: lint.MergeIfAny,
33 },
34 })
35
36 var Analyzer = SCAnalyzer.Analyzer
37
38 func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool {
39 call := node.(*ast.CallExpr)
40 typeconv, ok := call.Args[0].(*ast.CallExpr)
41 if !ok {
42 return true
43 }
44
45 sel, ok := typeconv.Fun.(*ast.SelectorExpr)
46 if !ok {
47 return true
48 }
49 name := code.SelectorName(pass, sel)
50 switch name {
51 case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice":
52 default:
53 return true
54 }
55
56 return false
57 }
58
59 func run(pass *analysis.Pass) (interface{}, error) {
60 type Error struct {
61 node ast.Node
62 msg string
63 }
64 var allErrors []Error
65 fn := func(node ast.Node) {
66 var body *ast.BlockStmt
67 switch node := node.(type) {
68 case *ast.FuncLit:
69 body = node.Body
70 case *ast.FuncDecl:
71 body = node.Body
72 default:
73 lint.ExhaustiveTypeSwitch(node)
74 }
75 if body == nil {
76 return
77 }
78
79 var errors []Error
80 permissible := false
81 fnSorts := func(node ast.Node) bool {
82 if permissible {
83 return false
84 }
85 if !code.IsCallTo(pass, node, "sort.Sort") {
86 return true
87 }
88 if isPermissibleSort(pass, node) {
89 permissible = true
90 return false
91 }
92 call := node.(*ast.CallExpr)
93 // isPermissibleSort guarantees that this type assertion will succeed
94 typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr)
95 sel := typeconv.Fun.(*ast.SelectorExpr)
96 name := code.SelectorName(pass, sel)
97
98 switch name {
99 case "sort.IntSlice":
100 errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"})
101 case "sort.Float64Slice":
102 errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"})
103 case "sort.StringSlice":
104 errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"})
105 }
106 return true
107 }
108 ast.Inspect(body, fnSorts)
109
110 if permissible {
111 return
112 }
113 allErrors = append(allErrors, errors...)
114 }
115 code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil))
116 sort.Slice(allErrors, func(i, j int) bool {
117 return allErrors[i].node.Pos() < allErrors[j].node.Pos()
118 })
119 var prev token.Pos
120 for _, err := range allErrors {
121 if err.node.Pos() == prev {
122 continue
123 }
124 prev = err.node.Pos()
125 report.Report(pass, err.node, err.msg, report.FilterGenerated())
126 }
127 return nil, nil
128 }
129