go/analysis: add sortslice pass

The sort.Slice method accepts an empty interface as its first
argument, but a slice type is the only valid use of the method.
This analyzer adds a diagnostic if the user uses the sort.Slice
method with anything other than a slice type as the first argument.

Change-Id: I3b54873faba2e9c2e832223a3cdab15a0b534650
Reviewed-on: https://go-review.googlesource.com/c/tools/+/191598
Run-TryBot: Johan Brandhorst <johan.brandhorst@gmail.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Johan Brandhorst 2019-08-24 17:28:48 +01:00 committed by Michael Matloob
parent 0240832f5c
commit 31e00f45c2
3 changed files with 190 additions and 0 deletions

View File

@ -0,0 +1,123 @@
// 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
}

View File

@ -0,0 +1,13 @@
package sortslice_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/sortslice"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, sortslice.Analyzer, "a")
}

View File

@ -0,0 +1,54 @@
package a
import "sort"
// IncorrectSort tries to sort an integer.
func IncorrectSort() {
i := 5
sortFn := func(i, j int) bool { return false }
sort.Slice(i, sortFn) // want "sort.Slice's argument must be a slice; is called with int"
}
// CorrectSort sorts integers. It should not produce a diagnostic.
func CorrectSort() {
s := []int{2, 3, 5, 6}
sortFn := func(i, j int) bool { return s[i] < s[j] }
sort.Slice(s, sortFn)
}
// CorrectInterface sorts an interface with a slice
// as the concrete type. It should not produce a diagnostic.
func CorrectInterface() {
var s interface{}
s = interface{}([]int{2, 1, 0})
sortFn := func(i, j int) bool { return s.([]int)[i] < s.([]int)[j] }
sort.Slice(s, sortFn)
}
type slicecompare interface {
compare(i, j int) bool
}
type intslice []int
func (s intslice) compare(i, j int) bool {
return s[i] < s[j]
}
// UnderlyingInterface sorts an interface with a slice
// as the concrete type. It should not produce a diagnostic.
func UnderlyingInterface() {
var s slicecompare
s = intslice([]int{2, 1, 0})
sort.Slice(s, s.compare)
}
type mySlice []int
// UnderlyingSlice sorts a type with an underlying type of
// slice of ints. It should not produce a diagnostic.
func UnderlyingSlice() {
s := mySlice{2, 3, 5, 6}
sortFn := func(i, j int) bool { return s[i] < s[j] }
sort.Slice(s, sortFn)
}