1
2
3
4
5
6
7 package sortslice
8
9 import (
10 "bytes"
11 "fmt"
12 "go/ast"
13 "go/format"
14 "go/types"
15
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/passes/inspect"
18 "golang.org/x/tools/go/ast/inspector"
19 "golang.org/x/tools/go/types/typeutil"
20 )
21
22 const Doc = `check the argument type of sort.Slice
23
24 sort.Slice requires an argument of a slice type. Check that
25 the interface{} value passed to sort.Slice is actually a slice.`
26
27 var Analyzer = &analysis.Analyzer{
28 Name: "sortslice",
29 Doc: Doc,
30 Requires: []*analysis.Analyzer{inspect.Analyzer},
31 Run: run,
32 }
33
34 func run(pass *analysis.Pass) (interface{}, error) {
35 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36
37 nodeFilter := []ast.Node{
38 (*ast.CallExpr)(nil),
39 }
40
41 inspect.Preorder(nodeFilter, func(n ast.Node) {
42 call := n.(*ast.CallExpr)
43 fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
44 if fn == nil {
45 return
46 }
47
48 fnName := fn.FullName()
49 if fnName != "sort.Slice" && fnName != "sort.SliceStable" && fnName != "sort.SliceIsSorted" {
50 return
51 }
52
53 arg := call.Args[0]
54 typ := pass.TypesInfo.Types[arg].Type
55
56 if tuple, ok := typ.(*types.Tuple); ok {
57 typ = tuple.At(0).Type()
58 }
59
60 switch typ.Underlying().(type) {
61 case *types.Slice, *types.Interface:
62 return
63 }
64
65
66
67 typ = pass.TypesInfo.Types[arg].Type
68
69 var fixes []analysis.SuggestedFix
70 switch v := typ.Underlying().(type) {
71 case *types.Array:
72 var buf bytes.Buffer
73 format.Node(&buf, pass.Fset, &ast.SliceExpr{
74 X: arg,
75 Slice3: false,
76 Lbrack: arg.End() + 1,
77 Rbrack: arg.End() + 3,
78 })
79 fixes = append(fixes, analysis.SuggestedFix{
80 Message: "Get a slice of the full array",
81 TextEdits: []analysis.TextEdit{{
82 Pos: arg.Pos(),
83 End: arg.End(),
84 NewText: buf.Bytes(),
85 }},
86 })
87 case *types.Pointer:
88 _, ok := v.Elem().Underlying().(*types.Slice)
89 if !ok {
90 break
91 }
92 var buf bytes.Buffer
93 format.Node(&buf, pass.Fset, &ast.StarExpr{
94 X: arg,
95 })
96 fixes = append(fixes, analysis.SuggestedFix{
97 Message: "Dereference the pointer to the slice",
98 TextEdits: []analysis.TextEdit{{
99 Pos: arg.Pos(),
100 End: arg.End(),
101 NewText: buf.Bytes(),
102 }},
103 })
104 case *types.Signature:
105 if v.Params().Len() != 0 || v.Results().Len() != 1 {
106 break
107 }
108 if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
109 break
110 }
111 var buf bytes.Buffer
112 format.Node(&buf, pass.Fset, &ast.CallExpr{
113 Fun: arg,
114 })
115 fixes = append(fixes, analysis.SuggestedFix{
116 Message: "Call the function",
117 TextEdits: []analysis.TextEdit{{
118 Pos: arg.Pos(),
119 End: arg.End(),
120 NewText: buf.Bytes(),
121 }},
122 })
123 }
124
125 pass.Report(analysis.Diagnostic{
126 Pos: call.Pos(),
127 End: call.End(),
128 Message: fmt.Sprintf("%s's argument must be a slice; is called with %s", fnName, typ.String()),
129 SuggestedFixes: fixes,
130 })
131 })
132 return nil, nil
133 }
134
View as plain text