mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
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:
parent
0240832f5c
commit
31e00f45c2
123
go/analysis/passes/sortslice/analyzer.go
Normal file
123
go/analysis/passes/sortslice/analyzer.go
Normal 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
|
||||||
|
}
|
13
go/analysis/passes/sortslice/analyzer_test.go
Normal file
13
go/analysis/passes/sortslice/analyzer_test.go
Normal 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")
|
||||||
|
}
|
54
go/analysis/passes/sortslice/testdata/src/a/a.go
vendored
Normal file
54
go/analysis/passes/sortslice/testdata/src/a/a.go
vendored
Normal 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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user