// 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 }