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