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