sa4029.go raw
1 package sa4029
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/types"
7
8 "honnef.co/go/tools/analysis/code"
9 "honnef.co/go/tools/analysis/edit"
10 "honnef.co/go/tools/analysis/lint"
11 "honnef.co/go/tools/analysis/report"
12 "honnef.co/go/tools/pattern"
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: "SA4029",
21 Run: run,
22 Requires: []*analysis.Analyzer{inspect.Analyzer},
23 },
24 Doc: &lint.RawDocumentation{
25 Title: "Ineffective attempt at sorting slice",
26 Text: `
27 \'sort.Float64Slice\', \'sort.IntSlice\', and \'sort.StringSlice\' are
28 types, not functions. Doing \'x = sort.StringSlice(x)\' does nothing,
29 especially not sort any values. The correct usage is
30 \'sort.Sort(sort.StringSlice(x))\' or \'sort.StringSlice(x).Sort()\',
31 but there are more convenient helpers, namely \'sort.Float64s\',
32 \'sort.Ints\', and \'sort.Strings\'.
33 `,
34 Since: "2022.1",
35 Severity: lint.SeverityWarning,
36 MergeIf: lint.MergeIfAny,
37 },
38 })
39
40 var Analyzer = SCAnalyzer.Analyzer
41
42 var ineffectiveSortQ = pattern.MustParse(`(AssignStmt target@(Ident _) "=" (CallExpr typ@(Symbol (Or "sort.Float64Slice" "sort.IntSlice" "sort.StringSlice")) [target]))`)
43
44 func run(pass *analysis.Pass) (interface{}, error) {
45 fn := func(node ast.Node) {
46 m, ok := code.Match(pass, ineffectiveSortQ, node)
47 if !ok {
48 return
49 }
50
51 _, ok = types.Unalias(pass.TypesInfo.TypeOf(m.State["target"].(ast.Expr))).(*types.Slice)
52 if !ok {
53 // Avoid flagging 'x = sort.StringSlice(x)' where TypeOf(x) == sort.StringSlice
54 return
55 }
56
57 var alternative string
58 typeName := types.TypeString(types.Unalias(m.State["typ"].(*types.TypeName).Type()), nil)
59 switch typeName {
60 case "sort.Float64Slice":
61 alternative = "Float64s"
62 case "sort.IntSlice":
63 alternative = "Ints"
64 case "sort.StringSlice":
65 alternative = "Strings"
66 default:
67 panic(fmt.Sprintf("unreachable: %q", typeName))
68 }
69
70 r := &ast.CallExpr{
71 Fun: &ast.SelectorExpr{
72 X: &ast.Ident{Name: "sort"},
73 Sel: &ast.Ident{Name: alternative},
74 },
75 Args: []ast.Expr{m.State["target"].(ast.Expr)},
76 }
77
78 report.Report(pass, node,
79 fmt.Sprintf("%s is a type, not a function, and %s doesn't sort your values; consider using sort.%s instead",
80 typeName,
81 report.Render(pass, node.(*ast.AssignStmt).Rhs[0]),
82 alternative),
83 report.Fixes(edit.Fix(fmt.Sprintf("replace with call to sort.%s", alternative), edit.ReplaceWithNode(pass.Fset, node, r))))
84 }
85 code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
86 return nil, nil
87 }
88