diff --git a/go/analysis/passes/sortslice/analyzer.go b/go/analysis/passes/sortslice/analyzer.go new file mode 100644 index 0000000000..69a67939d7 --- /dev/null +++ b/go/analysis/passes/sortslice/analyzer.go @@ -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 +} diff --git a/go/analysis/passes/sortslice/analyzer_test.go b/go/analysis/passes/sortslice/analyzer_test.go new file mode 100644 index 0000000000..9f81fe17e3 --- /dev/null +++ b/go/analysis/passes/sortslice/analyzer_test.go @@ -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") +} diff --git a/go/analysis/passes/sortslice/testdata/src/a/a.go b/go/analysis/passes/sortslice/testdata/src/a/a.go new file mode 100644 index 0000000000..3403660645 --- /dev/null +++ b/go/analysis/passes/sortslice/testdata/src/a/a.go @@ -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) +}