--- /dev/null
+// 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/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,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ 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 fn == nil {
+ return
+ }
+
+ if fn.FullName() != "sort.Slice" {
+ return
+ }
+
+ arg := call.Args[0]
+ typ := pass.TypesInfo.Types[arg].Type
+ switch typ.Underlying().(type) {
+ case *types.Slice, *types.Interface:
+ return
+ }
+
+ 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("sort.Slice's argument must be a slice; is called with %s", typ.String()),
+ SuggestedFixes: fixes,
+ })
+ })
+ return nil, nil
+}