| 1 | // Copyright 2019 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // Package sortslice defines an Analyzer that checks for calls |
| 6 | // to sort.Slice that do not use a slice type as first argument. |
| 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() // special case for Slice(f(...)) |
| 58 | } |
| 59 | |
| 60 | switch typ.Underlying().(type) { |
| 61 | case *types.Slice, *types.Interface: |
| 62 | return |
| 63 | } |
| 64 | |
| 65 | // Restore typ to the original type, we may unwrap the tuple above, |
| 66 | // typ might not be the type of arg. |
| 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 |
Members