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