govet: add more analyzers
This commit is contained in:
parent
6cc10f0615
commit
7e09842e2b
@ -13,18 +13,26 @@ import (
|
||||
"golang.org/x/tools/go/analysis/passes/atomic"
|
||||
"golang.org/x/tools/go/analysis/passes/atomicalign"
|
||||
"golang.org/x/tools/go/analysis/passes/bools"
|
||||
"golang.org/x/tools/go/analysis/passes/buildssa"
|
||||
"golang.org/x/tools/go/analysis/passes/buildtag"
|
||||
"golang.org/x/tools/go/analysis/passes/cgocall"
|
||||
"golang.org/x/tools/go/analysis/passes/composite"
|
||||
"golang.org/x/tools/go/analysis/passes/copylock"
|
||||
"golang.org/x/tools/go/analysis/passes/ctrlflow"
|
||||
"golang.org/x/tools/go/analysis/passes/deepequalerrors"
|
||||
"golang.org/x/tools/go/analysis/passes/errorsas"
|
||||
"golang.org/x/tools/go/analysis/passes/findcall"
|
||||
"golang.org/x/tools/go/analysis/passes/httpresponse"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/analysis/passes/loopclosure"
|
||||
"golang.org/x/tools/go/analysis/passes/lostcancel"
|
||||
"golang.org/x/tools/go/analysis/passes/nilfunc"
|
||||
"golang.org/x/tools/go/analysis/passes/nilness"
|
||||
"golang.org/x/tools/go/analysis/passes/pkgfact"
|
||||
"golang.org/x/tools/go/analysis/passes/printf"
|
||||
"golang.org/x/tools/go/analysis/passes/shadow"
|
||||
"golang.org/x/tools/go/analysis/passes/shift"
|
||||
"golang.org/x/tools/go/analysis/passes/sortslice"
|
||||
"golang.org/x/tools/go/analysis/passes/stdmethods"
|
||||
"golang.org/x/tools/go/analysis/passes/structtag"
|
||||
"golang.org/x/tools/go/analysis/passes/tests"
|
||||
@ -41,18 +49,26 @@ func getAllAnalyzers() []*analysis.Analyzer {
|
||||
atomic.Analyzer,
|
||||
atomicalign.Analyzer,
|
||||
bools.Analyzer,
|
||||
buildssa.Analyzer,
|
||||
buildtag.Analyzer,
|
||||
cgocall.Analyzer,
|
||||
composite.Analyzer,
|
||||
copylock.Analyzer,
|
||||
ctrlflow.Analyzer,
|
||||
deepequalerrors.Analyzer,
|
||||
errorsas.Analyzer,
|
||||
findcall.Analyzer,
|
||||
httpresponse.Analyzer,
|
||||
inspect.Analyzer,
|
||||
loopclosure.Analyzer,
|
||||
lostcancel.Analyzer,
|
||||
nilfunc.Analyzer,
|
||||
nilness.Analyzer,
|
||||
pkgfact.Analyzer,
|
||||
printf.Analyzer,
|
||||
shadow.Analyzer,
|
||||
shift.Analyzer,
|
||||
sortslice.Analyzer,
|
||||
stdmethods.Analyzer,
|
||||
structtag.Analyzer,
|
||||
tests.Analyzer,
|
||||
|
115
vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go
generated
vendored
Normal file
115
vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// 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 deepequalerrors defines an Analyzer that checks for the use
|
||||
// of reflect.DeepEqual with error values.
|
||||
package deepequalerrors
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"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 for calls of reflect.DeepEqual on error values
|
||||
|
||||
The deepequalerrors checker looks for calls of the form:
|
||||
|
||||
reflect.DeepEqual(err1, err2)
|
||||
|
||||
where err1 and err2 are errors. Using reflect.DeepEqual to compare
|
||||
errors is discouraged.`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "deepequalerrors",
|
||||
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, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if fn.FullName() == "reflect.DeepEqual" && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
|
||||
pass.Reportf(call.Pos(), "avoid using reflect.DeepEqual with errors")
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
|
||||
|
||||
// hasError reports whether the type of e contains the type error.
|
||||
// See containsError, below, for the meaning of "contains".
|
||||
func hasError(pass *analysis.Pass, e ast.Expr) bool {
|
||||
tv, ok := pass.TypesInfo.Types[e]
|
||||
if !ok { // no type info, assume good
|
||||
return false
|
||||
}
|
||||
return containsError(tv.Type)
|
||||
}
|
||||
|
||||
// Report whether any type that typ could store and that could be compared is the
|
||||
// error type. This includes typ itself, as well as the types of struct field, slice
|
||||
// and array elements, map keys and elements, and pointers. It does not include
|
||||
// channel types (incomparable), arg and result types of a Signature (not stored), or
|
||||
// methods of a named or interface type (not stored).
|
||||
func containsError(typ types.Type) bool {
|
||||
// Track types being processed, to avoid infinite recursion.
|
||||
// Using types as keys here is OK because we are checking for the identical pointer, not
|
||||
// type identity. See analysis/passes/printf/types.go.
|
||||
inProgress := make(map[types.Type]bool)
|
||||
|
||||
var check func(t types.Type) bool
|
||||
check = func(t types.Type) bool {
|
||||
if t == errorType {
|
||||
return true
|
||||
}
|
||||
if inProgress[t] {
|
||||
return false
|
||||
}
|
||||
inProgress[t] = true
|
||||
switch t := t.(type) {
|
||||
case *types.Pointer:
|
||||
return check(t.Elem())
|
||||
case *types.Slice:
|
||||
return check(t.Elem())
|
||||
case *types.Array:
|
||||
return check(t.Elem())
|
||||
case *types.Map:
|
||||
return check(t.Key()) || check(t.Elem())
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if check(t.Field(i).Type()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *types.Named:
|
||||
return check(t.Underlying())
|
||||
|
||||
// We list the remaining valid type kinds for completeness.
|
||||
case *types.Basic:
|
||||
case *types.Chan: // channels store values, but they are not comparable
|
||||
case *types.Signature:
|
||||
case *types.Tuple: // tuples are only part of signatures
|
||||
case *types.Interface:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return check(typ)
|
||||
}
|
80
vendor/golang.org/x/tools/go/analysis/passes/findcall/findcall.go
generated
vendored
Normal file
80
vendor/golang.org/x/tools/go/analysis/passes/findcall/findcall.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
// The findcall package defines an Analyzer that serves as a trivial
|
||||
// example and test of the Analysis API. It reports a diagnostic for
|
||||
// every call to a function or method of the name specified by its
|
||||
// -name flag.
|
||||
package findcall
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
const Doc = `find calls to a particular function
|
||||
|
||||
The findcall analysis reports calls to functions or methods
|
||||
of a particular name.`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "findcall",
|
||||
Doc: Doc,
|
||||
Run: run,
|
||||
RunDespiteErrors: true,
|
||||
FactTypes: []analysis.Fact{new(foundFact)},
|
||||
}
|
||||
|
||||
var name string // -name flag
|
||||
|
||||
func init() {
|
||||
Analyzer.Flags.StringVar(&name, "name", name, "name of the function to find")
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, f := range pass.Files {
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
var id *ast.Ident
|
||||
switch fun := call.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
id = fun
|
||||
case *ast.SelectorExpr:
|
||||
id = fun.Sel
|
||||
}
|
||||
if id != nil && !pass.TypesInfo.Types[id].IsType() && id.Name == name {
|
||||
pass.Reportf(call.Lparen, "call of %s(...)", id.Name)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Export a fact for each matching function.
|
||||
//
|
||||
// These facts are produced only to test the testing
|
||||
// infrastructure in the analysistest package.
|
||||
// They are not consumed by the findcall Analyzer
|
||||
// itself, as would happen in a more realistic example.
|
||||
for _, f := range pass.Files {
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name.Name == name {
|
||||
if obj, ok := pass.TypesInfo.Defs[decl.Name].(*types.Func); ok {
|
||||
pass.ExportObjectFact(obj, new(foundFact))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// foundFact is a fact associated with functions that match -name.
|
||||
// We use it to exercise the fact machinery in tests.
|
||||
type foundFact struct{}
|
||||
|
||||
func (*foundFact) String() string { return "found" }
|
||||
func (*foundFact) AFact() {}
|
271
vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go
generated
vendored
Normal file
271
vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go
generated
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
// Copyright 2018 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 nilness inspects the control-flow graph of an SSA function
|
||||
// and reports errors such as nil pointer dereferences and degenerate
|
||||
// nil pointer comparisons.
|
||||
package nilness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/buildssa"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
const Doc = `check for redundant or impossible nil comparisons
|
||||
|
||||
The nilness checker inspects the control-flow graph of each function in
|
||||
a package and reports nil pointer dereferences and degenerate nil
|
||||
pointers. A degenerate comparison is of the form x==nil or x!=nil where x
|
||||
is statically known to be nil or non-nil. These are often a mistake,
|
||||
especially in control flow related to errors.
|
||||
|
||||
This check reports conditions such as:
|
||||
|
||||
if f == nil { // impossible condition (f is a function)
|
||||
}
|
||||
|
||||
and:
|
||||
|
||||
p := &v
|
||||
...
|
||||
if p != nil { // tautological condition
|
||||
}
|
||||
|
||||
and:
|
||||
|
||||
if p == nil {
|
||||
print(*p) // nil dereference
|
||||
}
|
||||
`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "nilness",
|
||||
Doc: Doc,
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildssa.Analyzer},
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
|
||||
for _, fn := range ssainput.SrcFuncs {
|
||||
runFunc(pass, fn)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func runFunc(pass *analysis.Pass, fn *ssa.Function) {
|
||||
reportf := func(category string, pos token.Pos, format string, args ...interface{}) {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: pos,
|
||||
Category: category,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
// notNil reports an error if v is provably nil.
|
||||
notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
|
||||
if nilnessOf(stack, v) == isnil {
|
||||
reportf("nilderef", instr.Pos(), "nil dereference in "+descr)
|
||||
}
|
||||
}
|
||||
|
||||
// visit visits reachable blocks of the CFG in dominance order,
|
||||
// maintaining a stack of dominating nilness facts.
|
||||
//
|
||||
// By traversing the dom tree, we can pop facts off the stack as
|
||||
// soon as we've visited a subtree. Had we traversed the CFG,
|
||||
// we would need to retain the set of facts for each block.
|
||||
seen := make([]bool, len(fn.Blocks)) // seen[i] means visit should ignore block i
|
||||
var visit func(b *ssa.BasicBlock, stack []fact)
|
||||
visit = func(b *ssa.BasicBlock, stack []fact) {
|
||||
if seen[b.Index] {
|
||||
return
|
||||
}
|
||||
seen[b.Index] = true
|
||||
|
||||
// Report nil dereferences.
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
notNil(stack, instr, instr.Common().Value,
|
||||
instr.Common().Description())
|
||||
case *ssa.FieldAddr:
|
||||
notNil(stack, instr, instr.X, "field selection")
|
||||
case *ssa.IndexAddr:
|
||||
notNil(stack, instr, instr.X, "index operation")
|
||||
case *ssa.MapUpdate:
|
||||
notNil(stack, instr, instr.Map, "map update")
|
||||
case *ssa.Slice:
|
||||
// A nilcheck occurs in ptr[:] iff ptr is a pointer to an array.
|
||||
if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
|
||||
notNil(stack, instr, instr.X, "slice operation")
|
||||
}
|
||||
case *ssa.Store:
|
||||
notNil(stack, instr, instr.Addr, "store")
|
||||
case *ssa.TypeAssert:
|
||||
notNil(stack, instr, instr.X, "type assertion")
|
||||
case *ssa.UnOp:
|
||||
if instr.Op == token.MUL { // *X
|
||||
notNil(stack, instr, instr.X, "load")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For nil comparison blocks, report an error if the condition
|
||||
// is degenerate, and push a nilness fact on the stack when
|
||||
// visiting its true and false successor blocks.
|
||||
if binop, tsucc, fsucc := eq(b); binop != nil {
|
||||
xnil := nilnessOf(stack, binop.X)
|
||||
ynil := nilnessOf(stack, binop.Y)
|
||||
|
||||
if ynil != unknown && xnil != unknown && (xnil == isnil || ynil == isnil) {
|
||||
// Degenerate condition:
|
||||
// the nilness of both operands is known,
|
||||
// and at least one of them is nil.
|
||||
var adj string
|
||||
if (xnil == ynil) == (binop.Op == token.EQL) {
|
||||
adj = "tautological"
|
||||
} else {
|
||||
adj = "impossible"
|
||||
}
|
||||
reportf("cond", binop.Pos(), "%s condition: %s %s %s", adj, xnil, binop.Op, ynil)
|
||||
|
||||
// If tsucc's or fsucc's sole incoming edge is impossible,
|
||||
// it is unreachable. Prune traversal of it and
|
||||
// all the blocks it dominates.
|
||||
// (We could be more precise with full dataflow
|
||||
// analysis of control-flow joins.)
|
||||
var skip *ssa.BasicBlock
|
||||
if xnil == ynil {
|
||||
skip = fsucc
|
||||
} else {
|
||||
skip = tsucc
|
||||
}
|
||||
for _, d := range b.Dominees() {
|
||||
if d == skip && len(d.Preds) == 1 {
|
||||
continue
|
||||
}
|
||||
visit(d, stack)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// "if x == nil" or "if nil == y" condition; x, y are unknown.
|
||||
if xnil == isnil || ynil == isnil {
|
||||
var f fact
|
||||
if xnil == isnil {
|
||||
// x is nil, y is unknown:
|
||||
// t successor learns y is nil.
|
||||
f = fact{binop.Y, isnil}
|
||||
} else {
|
||||
// x is nil, y is unknown:
|
||||
// t successor learns x is nil.
|
||||
f = fact{binop.X, isnil}
|
||||
}
|
||||
|
||||
for _, d := range b.Dominees() {
|
||||
// Successor blocks learn a fact
|
||||
// only at non-critical edges.
|
||||
// (We could do be more precise with full dataflow
|
||||
// analysis of control-flow joins.)
|
||||
s := stack
|
||||
if len(d.Preds) == 1 {
|
||||
if d == tsucc {
|
||||
s = append(s, f)
|
||||
} else if d == fsucc {
|
||||
s = append(s, f.negate())
|
||||
}
|
||||
}
|
||||
visit(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range b.Dominees() {
|
||||
visit(d, stack)
|
||||
}
|
||||
}
|
||||
|
||||
// Visit the entry block. No need to visit fn.Recover.
|
||||
if fn.Blocks != nil {
|
||||
visit(fn.Blocks[0], make([]fact, 0, 20)) // 20 is plenty
|
||||
}
|
||||
}
|
||||
|
||||
// A fact records that a block is dominated
|
||||
// by the condition v == nil or v != nil.
|
||||
type fact struct {
|
||||
value ssa.Value
|
||||
nilness nilness
|
||||
}
|
||||
|
||||
func (f fact) negate() fact { return fact{f.value, -f.nilness} }
|
||||
|
||||
type nilness int
|
||||
|
||||
const (
|
||||
isnonnil = -1
|
||||
unknown nilness = 0
|
||||
isnil = 1
|
||||
)
|
||||
|
||||
var nilnessStrings = []string{"non-nil", "unknown", "nil"}
|
||||
|
||||
func (n nilness) String() string { return nilnessStrings[n+1] }
|
||||
|
||||
// nilnessOf reports whether v is definitely nil, definitely not nil,
|
||||
// or unknown given the dominating stack of facts.
|
||||
func nilnessOf(stack []fact, v ssa.Value) nilness {
|
||||
// Is value intrinsically nil or non-nil?
|
||||
switch v := v.(type) {
|
||||
case *ssa.Alloc,
|
||||
*ssa.FieldAddr,
|
||||
*ssa.FreeVar,
|
||||
*ssa.Function,
|
||||
*ssa.Global,
|
||||
*ssa.IndexAddr,
|
||||
*ssa.MakeChan,
|
||||
*ssa.MakeClosure,
|
||||
*ssa.MakeInterface,
|
||||
*ssa.MakeMap,
|
||||
*ssa.MakeSlice:
|
||||
return isnonnil
|
||||
case *ssa.Const:
|
||||
if v.IsNil() {
|
||||
return isnil
|
||||
} else {
|
||||
return isnonnil
|
||||
}
|
||||
}
|
||||
|
||||
// Search dominating control-flow facts.
|
||||
for _, f := range stack {
|
||||
if f.value == v {
|
||||
return f.nilness
|
||||
}
|
||||
}
|
||||
return unknown
|
||||
}
|
||||
|
||||
// If b ends with an equality comparison, eq returns the operation and
|
||||
// its true (equal) and false (not equal) successors.
|
||||
func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {
|
||||
if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
|
||||
if binop, ok := If.Cond.(*ssa.BinOp); ok {
|
||||
switch binop.Op {
|
||||
case token.EQL:
|
||||
return binop, b.Succs[0], b.Succs[1]
|
||||
case token.NEQ:
|
||||
return binop, b.Succs[1], b.Succs[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, nil
|
||||
}
|
127
vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go
generated
vendored
Normal file
127
vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
// The pkgfact package is a demonstration and test of the package fact
|
||||
// mechanism.
|
||||
//
|
||||
// The output of the pkgfact analysis is a set of key/values pairs
|
||||
// gathered from the analyzed package and its imported dependencies.
|
||||
// Each key/value pair comes from a top-level constant declaration
|
||||
// whose name starts and ends with "_". For example:
|
||||
//
|
||||
// package p
|
||||
//
|
||||
// const _greeting_ = "hello"
|
||||
// const _audience_ = "world"
|
||||
//
|
||||
// the pkgfact analysis output for package p would be:
|
||||
//
|
||||
// {"greeting": "hello", "audience": "world"}.
|
||||
//
|
||||
// In addition, the analysis reports a diagnostic at each import
|
||||
// showing which key/value pairs it contributes.
|
||||
package pkgfact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "pkgfact",
|
||||
Doc: "gather name/value pairs from constant declarations",
|
||||
Run: run,
|
||||
FactTypes: []analysis.Fact{new(pairsFact)},
|
||||
ResultType: reflect.TypeOf(map[string]string{}),
|
||||
}
|
||||
|
||||
// A pairsFact is a package-level fact that records
|
||||
// an set of key=value strings accumulated from constant
|
||||
// declarations in this package and its dependencies.
|
||||
// Elements are ordered by keys, which are unique.
|
||||
type pairsFact []string
|
||||
|
||||
func (f *pairsFact) AFact() {}
|
||||
func (f *pairsFact) String() string { return "pairs(" + strings.Join(*f, ", ") + ")" }
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
// At each import, print the fact from the imported
|
||||
// package and accumulate its information into the result.
|
||||
// (Warning: accumulation leads to quadratic growth of work.)
|
||||
doImport := func(spec *ast.ImportSpec) {
|
||||
pkg := imported(pass.TypesInfo, spec)
|
||||
var fact pairsFact
|
||||
if pass.ImportPackageFact(pkg, &fact) {
|
||||
for _, pair := range fact {
|
||||
eq := strings.IndexByte(pair, '=')
|
||||
result[pair[:eq]] = pair[1+eq:]
|
||||
}
|
||||
pass.Reportf(spec.Pos(), "%s", strings.Join(fact, " "))
|
||||
}
|
||||
}
|
||||
|
||||
// At each "const _name_ = value", add a fact into env.
|
||||
doConst := func(spec *ast.ValueSpec) {
|
||||
if len(spec.Names) == len(spec.Values) {
|
||||
for i := range spec.Names {
|
||||
name := spec.Names[i].Name
|
||||
if strings.HasPrefix(name, "_") && strings.HasSuffix(name, "_") {
|
||||
|
||||
if key := strings.Trim(name, "_"); key != "" {
|
||||
value := pass.TypesInfo.Types[spec.Values[i]].Value.String()
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range pass.Files {
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok {
|
||||
for _, spec := range decl.Specs {
|
||||
switch decl.Tok {
|
||||
case token.IMPORT:
|
||||
doImport(spec.(*ast.ImportSpec))
|
||||
case token.CONST:
|
||||
doConst(spec.(*ast.ValueSpec))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort/deduplicate the result and save it as a package fact.
|
||||
keys := make([]string, 0, len(result))
|
||||
for key := range result {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var fact pairsFact
|
||||
for _, key := range keys {
|
||||
fact = append(fact, fmt.Sprintf("%s=%s", key, result[key]))
|
||||
}
|
||||
if len(fact) > 0 {
|
||||
pass.ExportPackageFact(&fact)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
|
||||
obj, ok := info.Implicits[spec]
|
||||
if !ok {
|
||||
obj = info.Defs[spec.Name] // renaming import
|
||||
}
|
||||
return obj.(*types.PkgName).Imported()
|
||||
}
|
123
vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go
generated
vendored
Normal file
123
vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go
generated
vendored
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
|
||||
}
|
5
vendor/modules.txt
vendored
5
vendor/modules.txt
vendored
@ -194,16 +194,21 @@ golang.org/x/tools/go/analysis/passes/cgocall
|
||||
golang.org/x/tools/go/analysis/passes/composite
|
||||
golang.org/x/tools/go/analysis/passes/copylock
|
||||
golang.org/x/tools/go/analysis/passes/ctrlflow
|
||||
golang.org/x/tools/go/analysis/passes/deepequalerrors
|
||||
golang.org/x/tools/go/analysis/passes/errorsas
|
||||
golang.org/x/tools/go/analysis/passes/findcall
|
||||
golang.org/x/tools/go/analysis/passes/httpresponse
|
||||
golang.org/x/tools/go/analysis/passes/inspect
|
||||
golang.org/x/tools/go/analysis/passes/internal/analysisutil
|
||||
golang.org/x/tools/go/analysis/passes/loopclosure
|
||||
golang.org/x/tools/go/analysis/passes/lostcancel
|
||||
golang.org/x/tools/go/analysis/passes/nilfunc
|
||||
golang.org/x/tools/go/analysis/passes/nilness
|
||||
golang.org/x/tools/go/analysis/passes/pkgfact
|
||||
golang.org/x/tools/go/analysis/passes/printf
|
||||
golang.org/x/tools/go/analysis/passes/shadow
|
||||
golang.org/x/tools/go/analysis/passes/shift
|
||||
golang.org/x/tools/go/analysis/passes/sortslice
|
||||
golang.org/x/tools/go/analysis/passes/stdmethods
|
||||
golang.org/x/tools/go/analysis/passes/structtag
|
||||
golang.org/x/tools/go/analysis/passes/tests
|
||||
|
Loading…
x
Reference in New Issue
Block a user