Updated staticcheck

This commit is contained in:
kolaente 2019-04-22 12:59:42 +02:00
parent 5dc1fd0f86
commit 99f83542f6
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
34 changed files with 6144 additions and 2537 deletions

2
go.mod
View File

@ -73,6 +73,6 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/testfixtures.v2 v2.5.3
gopkg.in/yaml.v2 v2.2.2 // indirect
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c
)

3
go.sum
View File

@ -31,6 +31,7 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cweill/gotests v1.5.2 h1:kKqmKmS2wCV3tuLnfpbiuN8OlkosQZTpCfiqmiuNAsA=
github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -266,5 +267,7 @@ honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe h1:/GZ/onp6W295MEgrIwtlbnx
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c h1:z+UFwlQ7KVwdlQTE5JjvDvfZmyyAVrEiiwau20b7X8k=
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c h1:fTwL7EZ3ouk3xeiPiRBYEjSPWTREb9T57bjzpRBNOpQ=
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c/go.mod h1:B2NutmcRaDDw4EGe7DoCwyWCELA8W+KxXPhLtgqFUaU=

182
vendor/golang.org/x/tools/go/ast/inspector/inspector.go generated vendored Normal file
View File

@ -0,0 +1,182 @@
// 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 inspector provides helper functions for traversal over the
// syntax trees of a package, including node filtering by type, and
// materialization of the traversal stack.
//
// During construction, the inspector does a complete traversal and
// builds a list of push/pop events and their node type. Subsequent
// method calls that request a traversal scan this list, rather than walk
// the AST, and perform type filtering using efficient bit sets.
//
// Experiments suggest the inspector's traversals are about 2.5x faster
// than ast.Inspect, but it may take around 5 traversals for this
// benefit to amortize the inspector's construction cost.
// If efficiency is the primary concern, do not use use Inspector for
// one-off traversals.
package inspector
// There are four orthogonal features in a traversal:
// 1 type filtering
// 2 pruning
// 3 postorder calls to f
// 4 stack
// Rather than offer all of them in the API,
// only a few combinations are exposed:
// - Preorder is the fastest and has fewest features,
// but is the most commonly needed traversal.
// - Nodes and WithStack both provide pruning and postorder calls,
// even though few clients need it, because supporting two versions
// is not justified.
// More combinations could be supported by expressing them as
// wrappers around a more generic traversal, but this was measured
// and found to degrade performance significantly (30%).
import (
"go/ast"
)
// An Inspector provides methods for inspecting
// (traversing) the syntax trees of a package.
type Inspector struct {
events []event
}
// New returns an Inspector for the specified syntax trees.
func New(files []*ast.File) *Inspector {
return &Inspector{traverse(files)}
}
// An event represents a push or a pop
// of an ast.Node during a traversal.
type event struct {
node ast.Node
typ uint64 // typeOf(node)
index int // 1 + index of corresponding pop event, or 0 if this is a pop
}
// Preorder visits all the nodes of the files supplied to New in
// depth-first order. It calls f(n) for each node n before it visits
// n's children.
//
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// Because it avoids postorder calls to f, and the pruning
// check, Preorder is almost twice as fast as Nodes. The two
// features seem to contribute similar slowdowns (~1.4x each).
mask := maskOf(types)
for i := 0; i < len(in.events); {
ev := in.events[i]
if ev.typ&mask != 0 {
if ev.index > 0 {
f(ev.node)
}
}
i++
}
}
// Nodes visits the nodes of the files supplied to New in depth-first
// order. It calls f(n, true) for each node n before it visits n's
// children. If f returns true, Nodes invokes f recursively for each
// of the non-nil children of the node, followed by a call of
// f(n, false).
//
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (prune bool)) {
mask := maskOf(types)
for i := 0; i < len(in.events); {
ev := in.events[i]
if ev.typ&mask != 0 {
if ev.index > 0 {
// push
if !f(ev.node, true) {
i = ev.index // jump to corresponding pop + 1
continue
}
} else {
// pop
f(ev.node, false)
}
}
i++
}
}
// WithStack visits nodes in a similar manner to Nodes, but it
// supplies each call to f an additional argument, the current
// traversal stack. The stack's first element is the outermost node,
// an *ast.File; its last is the innermost, n.
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (prune bool)) {
mask := maskOf(types)
var stack []ast.Node
for i := 0; i < len(in.events); {
ev := in.events[i]
if ev.index > 0 {
// push
stack = append(stack, ev.node)
if ev.typ&mask != 0 {
if !f(ev.node, true, stack) {
i = ev.index
stack = stack[:len(stack)-1]
continue
}
}
} else {
// pop
if ev.typ&mask != 0 {
f(ev.node, false, stack)
}
stack = stack[:len(stack)-1]
}
i++
}
}
// traverse builds the table of events representing a traversal.
func traverse(files []*ast.File) []event {
// Preallocate approximate number of events
// based on source file extent.
// This makes traverse faster by 4x (!).
var extent int
for _, f := range files {
extent += int(f.End() - f.Pos())
}
// This estimate is based on the net/http package.
events := make([]event, 0, extent*33/100)
var stack []event
for _, f := range files {
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
// push
ev := event{
node: n,
typ: typeOf(n),
index: len(events), // push event temporarily holds own index
}
stack = append(stack, ev)
events = append(events, ev)
} else {
// pop
ev := stack[len(stack)-1]
stack = stack[:len(stack)-1]
events[ev.index].index = len(events) + 1 // make push refer to pop
ev.index = 0 // turn ev into a pop event
events = append(events, ev)
}
return true
})
}
return events
}

216
vendor/golang.org/x/tools/go/ast/inspector/typeof.go generated vendored Normal file
View File

@ -0,0 +1,216 @@
package inspector
// This file defines func typeOf(ast.Node) uint64.
//
// The initial map-based implementation was too slow;
// see https://go-review.googlesource.com/c/tools/+/135655/1/go/ast/inspector/inspector.go#196
import "go/ast"
const (
nArrayType = iota
nAssignStmt
nBadDecl
nBadExpr
nBadStmt
nBasicLit
nBinaryExpr
nBlockStmt
nBranchStmt
nCallExpr
nCaseClause
nChanType
nCommClause
nComment
nCommentGroup
nCompositeLit
nDeclStmt
nDeferStmt
nEllipsis
nEmptyStmt
nExprStmt
nField
nFieldList
nFile
nForStmt
nFuncDecl
nFuncLit
nFuncType
nGenDecl
nGoStmt
nIdent
nIfStmt
nImportSpec
nIncDecStmt
nIndexExpr
nInterfaceType
nKeyValueExpr
nLabeledStmt
nMapType
nPackage
nParenExpr
nRangeStmt
nReturnStmt
nSelectStmt
nSelectorExpr
nSendStmt
nSliceExpr
nStarExpr
nStructType
nSwitchStmt
nTypeAssertExpr
nTypeSpec
nTypeSwitchStmt
nUnaryExpr
nValueSpec
)
// typeOf returns a distinct single-bit value that represents the type of n.
//
// Various implementations were benchmarked with BenchmarkNewInspector:
// GOGC=off
// - type switch 4.9-5.5ms 2.1ms
// - binary search over a sorted list of types 5.5-5.9ms 2.5ms
// - linear scan, frequency-ordered list 5.9-6.1ms 2.7ms
// - linear scan, unordered list 6.4ms 2.7ms
// - hash table 6.5ms 3.1ms
// A perfect hash seemed like overkill.
//
// The compiler's switch statement is the clear winner
// as it produces a binary tree in code,
// with constant conditions and good branch prediction.
// (Sadly it is the most verbose in source code.)
// Binary search suffered from poor branch prediction.
//
func typeOf(n ast.Node) uint64 {
// Fast path: nearly half of all nodes are identifiers.
if _, ok := n.(*ast.Ident); ok {
return 1 << nIdent
}
// These cases include all nodes encountered by ast.Inspect.
switch n.(type) {
case *ast.ArrayType:
return 1 << nArrayType
case *ast.AssignStmt:
return 1 << nAssignStmt
case *ast.BadDecl:
return 1 << nBadDecl
case *ast.BadExpr:
return 1 << nBadExpr
case *ast.BadStmt:
return 1 << nBadStmt
case *ast.BasicLit:
return 1 << nBasicLit
case *ast.BinaryExpr:
return 1 << nBinaryExpr
case *ast.BlockStmt:
return 1 << nBlockStmt
case *ast.BranchStmt:
return 1 << nBranchStmt
case *ast.CallExpr:
return 1 << nCallExpr
case *ast.CaseClause:
return 1 << nCaseClause
case *ast.ChanType:
return 1 << nChanType
case *ast.CommClause:
return 1 << nCommClause
case *ast.Comment:
return 1 << nComment
case *ast.CommentGroup:
return 1 << nCommentGroup
case *ast.CompositeLit:
return 1 << nCompositeLit
case *ast.DeclStmt:
return 1 << nDeclStmt
case *ast.DeferStmt:
return 1 << nDeferStmt
case *ast.Ellipsis:
return 1 << nEllipsis
case *ast.EmptyStmt:
return 1 << nEmptyStmt
case *ast.ExprStmt:
return 1 << nExprStmt
case *ast.Field:
return 1 << nField
case *ast.FieldList:
return 1 << nFieldList
case *ast.File:
return 1 << nFile
case *ast.ForStmt:
return 1 << nForStmt
case *ast.FuncDecl:
return 1 << nFuncDecl
case *ast.FuncLit:
return 1 << nFuncLit
case *ast.FuncType:
return 1 << nFuncType
case *ast.GenDecl:
return 1 << nGenDecl
case *ast.GoStmt:
return 1 << nGoStmt
case *ast.Ident:
return 1 << nIdent
case *ast.IfStmt:
return 1 << nIfStmt
case *ast.ImportSpec:
return 1 << nImportSpec
case *ast.IncDecStmt:
return 1 << nIncDecStmt
case *ast.IndexExpr:
return 1 << nIndexExpr
case *ast.InterfaceType:
return 1 << nInterfaceType
case *ast.KeyValueExpr:
return 1 << nKeyValueExpr
case *ast.LabeledStmt:
return 1 << nLabeledStmt
case *ast.MapType:
return 1 << nMapType
case *ast.Package:
return 1 << nPackage
case *ast.ParenExpr:
return 1 << nParenExpr
case *ast.RangeStmt:
return 1 << nRangeStmt
case *ast.ReturnStmt:
return 1 << nReturnStmt
case *ast.SelectStmt:
return 1 << nSelectStmt
case *ast.SelectorExpr:
return 1 << nSelectorExpr
case *ast.SendStmt:
return 1 << nSendStmt
case *ast.SliceExpr:
return 1 << nSliceExpr
case *ast.StarExpr:
return 1 << nStarExpr
case *ast.StructType:
return 1 << nStructType
case *ast.SwitchStmt:
return 1 << nSwitchStmt
case *ast.TypeAssertExpr:
return 1 << nTypeAssertExpr
case *ast.TypeSpec:
return 1 << nTypeSpec
case *ast.TypeSwitchStmt:
return 1 << nTypeSwitchStmt
case *ast.UnaryExpr:
return 1 << nUnaryExpr
case *ast.ValueSpec:
return 1 << nValueSpec
}
return 0
}
func maskOf(nodes []ast.Node) uint64 {
if nodes == nil {
return 1<<64 - 1 // match all node types
}
var mask uint64
for _, n := range nodes {
mask |= typeOf(n)
}
return mask
}

View File

@ -20,11 +20,8 @@ func main() {
simple.NewChecker(),
staticcheck.NewChecker(),
stylecheck.NewChecker(),
&unused.Checker{},
}
uc := unused.NewChecker(unused.CheckAll)
uc.ConsiderReflection = true
checkers = append(checkers, unused.NewLintChecker(uc))
lintutil.ProcessFlagSet(checkers, fs)
}

View File

@ -82,7 +82,7 @@ var defaultConfig = Config{
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS",
"XSS", "SIP", "RTP",
},
DotImportWhitelist: []string{},
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},

View File

@ -5,6 +5,6 @@ initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS"]
"XSS", "SIP", "RTP"]
dot_import_whitelist = []
http_status_code_whitelist = ["200", "400", "404", "500"]

View File

@ -9,46 +9,104 @@ var Stdlib = map[string]Deprecation{
"image/jpeg.Reader": {4, 0},
// FIXME(dh): AllowBinary isn't being detected as deprecated
// because the comment has a newline right after "Deprecated:"
"go/build.AllowBinary": {7, 7},
"(archive/zip.FileHeader).CompressedSize": {1, 1},
"(archive/zip.FileHeader).UncompressedSize": {1, 1},
"(go/doc.Package).Bugs": {1, 1},
"os.SEEK_SET": {7, 7},
"os.SEEK_CUR": {7, 7},
"os.SEEK_END": {7, 7},
"(net.Dialer).Cancel": {7, 7},
"runtime.CPUProfile": {9, 0},
"compress/flate.ReadError": {6, 6},
"compress/flate.WriteError": {6, 6},
"path/filepath.HasPrefix": {0, 0},
"(net/http.Transport).Dial": {7, 7},
"(*net/http.Transport).CancelRequest": {6, 5},
"net/http.ErrWriteAfterFlush": {7, 0},
"net/http.ErrHeaderTooLong": {8, 0},
"net/http.ErrShortBody": {8, 0},
"net/http.ErrMissingContentLength": {8, 0},
"net/http/httputil.ErrPersistEOF": {0, 0},
"net/http/httputil.ErrClosed": {0, 0},
"net/http/httputil.ErrPipeline": {0, 0},
"net/http/httputil.ServerConn": {0, 0},
"net/http/httputil.NewServerConn": {0, 0},
"net/http/httputil.ClientConn": {0, 0},
"net/http/httputil.NewClientConn": {0, 0},
"net/http/httputil.NewProxyClientConn": {0, 0},
"(net/http.Request).Cancel": {7, 7},
"(text/template/parse.PipeNode).Line": {1, 1},
"(text/template/parse.ActionNode).Line": {1, 1},
"(text/template/parse.BranchNode).Line": {1, 1},
"(text/template/parse.TemplateNode).Line": {1, 1},
"database/sql/driver.ColumnConverter": {9, 9},
"database/sql/driver.Execer": {8, 8},
"database/sql/driver.Queryer": {8, 8},
"(database/sql/driver.Conn).Begin": {8, 8},
"(database/sql/driver.Stmt).Exec": {8, 8},
"(database/sql/driver.Stmt).Query": {8, 8},
"syscall.StringByteSlice": {1, 1},
"syscall.StringBytePtr": {1, 1},
"syscall.StringSlicePtr": {1, 1},
"syscall.StringToUTF16": {1, 1},
"syscall.StringToUTF16Ptr": {1, 1},
"go/build.AllowBinary": {7, 7},
"(archive/zip.FileHeader).CompressedSize": {1, 1},
"(archive/zip.FileHeader).UncompressedSize": {1, 1},
"(archive/zip.FileHeader).ModifiedTime": {10, 10},
"(archive/zip.FileHeader).ModifiedDate": {10, 10},
"(*archive/zip.FileHeader).ModTime": {10, 10},
"(*archive/zip.FileHeader).SetModTime": {10, 10},
"(go/doc.Package).Bugs": {1, 1},
"os.SEEK_SET": {7, 7},
"os.SEEK_CUR": {7, 7},
"os.SEEK_END": {7, 7},
"(net.Dialer).Cancel": {7, 7},
"runtime.CPUProfile": {9, 0},
"compress/flate.ReadError": {6, 6},
"compress/flate.WriteError": {6, 6},
"path/filepath.HasPrefix": {0, 0},
"(net/http.Transport).Dial": {7, 7},
"(*net/http.Transport).CancelRequest": {6, 5},
"net/http.ErrWriteAfterFlush": {7, 0},
"net/http.ErrHeaderTooLong": {8, 0},
"net/http.ErrShortBody": {8, 0},
"net/http.ErrMissingContentLength": {8, 0},
"net/http/httputil.ErrPersistEOF": {0, 0},
"net/http/httputil.ErrClosed": {0, 0},
"net/http/httputil.ErrPipeline": {0, 0},
"net/http/httputil.ServerConn": {0, 0},
"net/http/httputil.NewServerConn": {0, 0},
"net/http/httputil.ClientConn": {0, 0},
"net/http/httputil.NewClientConn": {0, 0},
"net/http/httputil.NewProxyClientConn": {0, 0},
"(net/http.Request).Cancel": {7, 7},
"(text/template/parse.PipeNode).Line": {1, 1},
"(text/template/parse.ActionNode).Line": {1, 1},
"(text/template/parse.BranchNode).Line": {1, 1},
"(text/template/parse.TemplateNode).Line": {1, 1},
"database/sql/driver.ColumnConverter": {9, 9},
"database/sql/driver.Execer": {8, 8},
"database/sql/driver.Queryer": {8, 8},
"(database/sql/driver.Conn).Begin": {8, 8},
"(database/sql/driver.Stmt).Exec": {8, 8},
"(database/sql/driver.Stmt).Query": {8, 8},
"syscall.StringByteSlice": {1, 1},
"syscall.StringBytePtr": {1, 1},
"syscall.StringSlicePtr": {1, 1},
"syscall.StringToUTF16": {1, 1},
"syscall.StringToUTF16Ptr": {1, 1},
"(*regexp.Regexp).Copy": {12, 12},
"(archive/tar.Header).Xattrs": {10, 10},
"archive/tar.TypeRegA": {11, 1},
"go/types.NewInterface": {11, 11},
"(*go/types.Interface).Embedded": {11, 11},
"go/importer.For": {12, 12},
"encoding/json.InvalidUTF8Error": {2, 2},
"encoding/json.UnmarshalFieldError": {2, 2},
"encoding/csv.ErrTrailingComma": {2, 2},
"(encoding/csv.Reader).TrailingComma": {2, 2},
"(net.Dialer).DualStack": {12, 12},
"net/http.ErrUnexpectedTrailer": {12, 12},
"net/http.CloseNotifier": {11, 7},
"net/http.ProtocolError": {8, 8},
"(crypto/x509.CertificateRequest).Attributes": {5, 3},
// This function has no alternative, but also no purpose.
"(*crypto/rc4.Cipher).Reset": {12, 0},
"(net/http/httptest.ResponseRecorder).HeaderMap": {11, 7},
// All of these have been deprecated in favour of external libraries
"syscall.AttachLsf": {7, 0},
"syscall.DetachLsf": {7, 0},
"syscall.LsfSocket": {7, 0},
"syscall.SetLsfPromisc": {7, 0},
"syscall.LsfJump": {7, 0},
"syscall.LsfStmt": {7, 0},
"syscall.BpfStmt": {7, 0},
"syscall.BpfJump": {7, 0},
"syscall.BpfBuflen": {7, 0},
"syscall.SetBpfBuflen": {7, 0},
"syscall.BpfDatalink": {7, 0},
"syscall.SetBpfDatalink": {7, 0},
"syscall.SetBpfPromisc": {7, 0},
"syscall.FlushBpf": {7, 0},
"syscall.BpfInterface": {7, 0},
"syscall.SetBpfInterface": {7, 0},
"syscall.BpfTimeout": {7, 0},
"syscall.SetBpfTimeout": {7, 0},
"syscall.BpfStats": {7, 0},
"syscall.SetBpfImmediate": {7, 0},
"syscall.SetBpf": {7, 0},
"syscall.CheckBpfVersion": {7, 0},
"syscall.BpfHeadercmpl": {7, 0},
"syscall.SetBpfHeadercmpl": {7, 0},
"syscall.RouteRIB": {8, 0},
"syscall.RoutingMessage": {8, 0},
"syscall.RouteMessage": {8, 0},
"syscall.InterfaceMessage": {8, 0},
"syscall.InterfaceAddrMessage": {8, 0},
"syscall.ParseRoutingMessage": {8, 0},
"syscall.ParseRoutingSockaddr": {8, 0},
"InterfaceAnnounceMessage": {7, 0},
"InterfaceMulticastAddrMessage": {7, 0},
"syscall.FormatMessage": {5, 0},
}

View File

@ -0,0 +1,46 @@
// 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 typeutil
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/ast/astutil"
)
// Callee returns the named target of a function call, if any:
// a function, method, builtin, or variable.
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
var obj types.Object
switch fun := astutil.Unparen(call.Fun).(type) {
case *ast.Ident:
obj = info.Uses[fun] // type, var, builtin, or declared func
case *ast.SelectorExpr:
if sel, ok := info.Selections[fun]; ok {
obj = sel.Obj() // method or field
} else {
obj = info.Uses[fun.Sel] // qualified identifier?
}
}
if _, ok := obj.(*types.TypeName); ok {
return nil // T(x) is a conversion, not a call
}
return obj
}
// StaticCallee returns the target (function or method) of a static
// function call, if any. It returns nil for calls to builtins.
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
if f, ok := Callee(info, call).(*types.Func); ok && !interfaceMethod(f) {
return f
}
return nil
}
func interfaceMethod(f *types.Func) bool {
recv := f.Type().(*types.Signature).Recv()
return recv != nil && types.IsInterface(recv.Type())
}

View File

@ -0,0 +1,29 @@
package typeutil
import (
"go/types"
)
// Identical reports whether x and y are identical types.
// Unlike types.Identical, receivers of Signature types are not ignored.
func Identical(x, y types.Type) (ret bool) {
if !types.Identical(x, y) {
return false
}
sigX, ok := x.(*types.Signature)
if !ok {
return true
}
sigY, ok := y.(*types.Signature)
if !ok {
// should be impossible
return true
}
if sigX.Recv() == sigY.Recv() {
return true
}
if sigX.Recv() == nil || sigY.Recv() == nil {
return false
}
return Identical(sigX.Recv().Type(), sigY.Recv().Type())
}

View File

@ -0,0 +1,31 @@
// Copyright 2014 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 typeutil
import "go/types"
// Dependencies returns all dependencies of the specified packages.
//
// Dependent packages appear in topological order: if package P imports
// package Q, Q appears earlier than P in the result.
// The algorithm follows import statements in the order they
// appear in the source code, so the result is a total order.
//
func Dependencies(pkgs ...*types.Package) []*types.Package {
var result []*types.Package
seen := make(map[*types.Package]bool)
var visit func(pkgs []*types.Package)
visit = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !seen[p] {
seen[p] = true
visit(p.Imports())
result = append(result, p)
}
}
}
visit(pkgs)
return result
}

View File

@ -0,0 +1,315 @@
// Copyright 2014 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 typeutil defines various utilities for types, such as Map,
// a mapping from types.Type to interface{} values.
package typeutil
import (
"bytes"
"fmt"
"go/types"
"reflect"
)
// Map is a hash-table-based mapping from types (types.Type) to
// arbitrary interface{} values. The concrete types that implement
// the Type interface are pointers. Since they are not canonicalized,
// == cannot be used to check for equivalence, and thus we cannot
// simply use a Go map.
//
// Just as with map[K]V, a nil *Map is a valid empty map.
//
// Not thread-safe.
//
// This fork handles Signatures correctly, respecting method receivers.
//
type Map struct {
hasher Hasher // shared by many Maps
table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused
length int // number of map entries
}
// entry is an entry (key/value association) in a hash bucket.
type entry struct {
key types.Type
value interface{}
}
// SetHasher sets the hasher used by Map.
//
// All Hashers are functionally equivalent but contain internal state
// used to cache the results of hashing previously seen types.
//
// A single Hasher created by MakeHasher() may be shared among many
// Maps. This is recommended if the instances have many keys in
// common, as it will amortize the cost of hash computation.
//
// A Hasher may grow without bound as new types are seen. Even when a
// type is deleted from the map, the Hasher never shrinks, since other
// types in the map may reference the deleted type indirectly.
//
// Hashers are not thread-safe, and read-only operations such as
// Map.Lookup require updates to the hasher, so a full Mutex lock (not a
// read-lock) is require around all Map operations if a shared
// hasher is accessed from multiple threads.
//
// If SetHasher is not called, the Map will create a private hasher at
// the first call to Insert.
//
func (m *Map) SetHasher(hasher Hasher) {
m.hasher = hasher
}
// Delete removes the entry with the given key, if any.
// It returns true if the entry was found.
//
func (m *Map) Delete(key types.Type) bool {
if m != nil && m.table != nil {
hash := m.hasher.Hash(key)
bucket := m.table[hash]
for i, e := range bucket {
if e.key != nil && Identical(key, e.key) {
// We can't compact the bucket as it
// would disturb iterators.
bucket[i] = entry{}
m.length--
return true
}
}
}
return false
}
// At returns the map entry for the given key.
// The result is nil if the entry is not present.
//
func (m *Map) At(key types.Type) interface{} {
if m != nil && m.table != nil {
for _, e := range m.table[m.hasher.Hash(key)] {
if e.key != nil && Identical(key, e.key) {
return e.value
}
}
}
return nil
}
// Set sets the map entry for key to val,
// and returns the previous entry, if any.
func (m *Map) Set(key types.Type, value interface{}) (prev interface{}) {
if m.table != nil {
hash := m.hasher.Hash(key)
bucket := m.table[hash]
var hole *entry
for i, e := range bucket {
if e.key == nil {
hole = &bucket[i]
} else if Identical(key, e.key) {
prev = e.value
bucket[i].value = value
return
}
}
if hole != nil {
*hole = entry{key, value} // overwrite deleted entry
} else {
m.table[hash] = append(bucket, entry{key, value})
}
} else {
if m.hasher.memo == nil {
m.hasher = MakeHasher()
}
hash := m.hasher.Hash(key)
m.table = map[uint32][]entry{hash: {entry{key, value}}}
}
m.length++
return
}
// Len returns the number of map entries.
func (m *Map) Len() int {
if m != nil {
return m.length
}
return 0
}
// Iterate calls function f on each entry in the map in unspecified order.
//
// If f should mutate the map, Iterate provides the same guarantees as
// Go maps: if f deletes a map entry that Iterate has not yet reached,
// f will not be invoked for it, but if f inserts a map entry that
// Iterate has not yet reached, whether or not f will be invoked for
// it is unspecified.
//
func (m *Map) Iterate(f func(key types.Type, value interface{})) {
if m != nil {
for _, bucket := range m.table {
for _, e := range bucket {
if e.key != nil {
f(e.key, e.value)
}
}
}
}
}
// Keys returns a new slice containing the set of map keys.
// The order is unspecified.
func (m *Map) Keys() []types.Type {
keys := make([]types.Type, 0, m.Len())
m.Iterate(func(key types.Type, _ interface{}) {
keys = append(keys, key)
})
return keys
}
func (m *Map) toString(values bool) string {
if m == nil {
return "{}"
}
var buf bytes.Buffer
fmt.Fprint(&buf, "{")
sep := ""
m.Iterate(func(key types.Type, value interface{}) {
fmt.Fprint(&buf, sep)
sep = ", "
fmt.Fprint(&buf, key)
if values {
fmt.Fprintf(&buf, ": %q", value)
}
})
fmt.Fprint(&buf, "}")
return buf.String()
}
// String returns a string representation of the map's entries.
// Values are printed using fmt.Sprintf("%v", v).
// Order is unspecified.
//
func (m *Map) String() string {
return m.toString(true)
}
// KeysString returns a string representation of the map's key set.
// Order is unspecified.
//
func (m *Map) KeysString() string {
return m.toString(false)
}
////////////////////////////////////////////////////////////////////////
// Hasher
// A Hasher maps each type to its hash value.
// For efficiency, a hasher uses memoization; thus its memory
// footprint grows monotonically over time.
// Hashers are not thread-safe.
// Hashers have reference semantics.
// Call MakeHasher to create a Hasher.
type Hasher struct {
memo map[types.Type]uint32
}
// MakeHasher returns a new Hasher instance.
func MakeHasher() Hasher {
return Hasher{make(map[types.Type]uint32)}
}
// Hash computes a hash value for the given type t such that
// Identical(t, t') => Hash(t) == Hash(t').
func (h Hasher) Hash(t types.Type) uint32 {
hash, ok := h.memo[t]
if !ok {
hash = h.hashFor(t)
h.memo[t] = hash
}
return hash
}
// hashString computes the FowlerNollVo hash of s.
func hashString(s string) uint32 {
var h uint32
for i := 0; i < len(s); i++ {
h ^= uint32(s[i])
h *= 16777619
}
return h
}
// hashFor computes the hash of t.
func (h Hasher) hashFor(t types.Type) uint32 {
// See Identical for rationale.
switch t := t.(type) {
case *types.Basic:
return uint32(t.Kind())
case *types.Array:
return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem())
case *types.Slice:
return 9049 + 2*h.Hash(t.Elem())
case *types.Struct:
var hash uint32 = 9059
for i, n := 0, t.NumFields(); i < n; i++ {
f := t.Field(i)
if f.Anonymous() {
hash += 8861
}
hash += hashString(t.Tag(i))
hash += hashString(f.Name()) // (ignore f.Pkg)
hash += h.Hash(f.Type())
}
return hash
case *types.Pointer:
return 9067 + 2*h.Hash(t.Elem())
case *types.Signature:
var hash uint32 = 9091
if t.Variadic() {
hash *= 8863
}
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
case *types.Interface:
var hash uint32 = 9103
for i, n := 0, t.NumMethods(); i < n; i++ {
// See go/types.identicalMethods for rationale.
// Method order is not significant.
// Ignore m.Pkg().
m := t.Method(i)
hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type())
}
return hash
case *types.Map:
return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem())
case *types.Chan:
return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem())
case *types.Named:
// Not safe with a copying GC; objects may move.
return uint32(reflect.ValueOf(t.Obj()).Pointer())
case *types.Tuple:
return h.hashTuple(t)
}
panic(t)
}
func (h Hasher) hashTuple(tuple *types.Tuple) uint32 {
// See go/types.identicalTypes for rationale.
n := tuple.Len()
var hash uint32 = 9137 + 2*uint32(n)
for i := 0; i < n; i++ {
hash += 3 * h.Hash(tuple.At(i).Type())
}
return hash
}

View File

@ -0,0 +1,72 @@
// Copyright 2014 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.
// This file implements a cache of method sets.
package typeutil
import (
"go/types"
"sync"
)
// A MethodSetCache records the method set of each type T for which
// MethodSet(T) is called so that repeat queries are fast.
// The zero value is a ready-to-use cache instance.
type MethodSetCache struct {
mu sync.Mutex
named map[*types.Named]struct{ value, pointer *types.MethodSet } // method sets for named N and *N
others map[types.Type]*types.MethodSet // all other types
}
// MethodSet returns the method set of type T. It is thread-safe.
//
// If cache is nil, this function is equivalent to types.NewMethodSet(T).
// Utility functions can thus expose an optional *MethodSetCache
// parameter to clients that care about performance.
//
func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet {
if cache == nil {
return types.NewMethodSet(T)
}
cache.mu.Lock()
defer cache.mu.Unlock()
switch T := T.(type) {
case *types.Named:
return cache.lookupNamed(T).value
case *types.Pointer:
if N, ok := T.Elem().(*types.Named); ok {
return cache.lookupNamed(N).pointer
}
}
// all other types
// (The map uses pointer equivalence, not type identity.)
mset := cache.others[T]
if mset == nil {
mset = types.NewMethodSet(T)
if cache.others == nil {
cache.others = make(map[types.Type]*types.MethodSet)
}
cache.others[T] = mset
}
return mset
}
func (cache *MethodSetCache) lookupNamed(named *types.Named) struct{ value, pointer *types.MethodSet } {
if cache.named == nil {
cache.named = make(map[*types.Named]struct{ value, pointer *types.MethodSet })
}
// Avoid recomputing mset(*T) for each distinct Pointer
// instance whose underlying type is a named type.
msets, ok := cache.named[named]
if !ok {
msets.value = types.NewMethodSet(named)
msets.pointer = types.NewMethodSet(types.NewPointer(named))
cache.named[named] = msets
}
return msets
}

View File

@ -0,0 +1,52 @@
// Copyright 2014 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 typeutil
// This file defines utilities for user interfaces that display types.
import "go/types"
// IntuitiveMethodSet returns the intuitive method set of a type T,
// which is the set of methods you can call on an addressable value of
// that type.
//
// The result always contains MethodSet(T), and is exactly MethodSet(T)
// for interface types and for pointer-to-concrete types.
// For all other concrete types T, the result additionally
// contains each method belonging to *T if there is no identically
// named method on T itself.
//
// This corresponds to user intuition about method sets;
// this function is intended only for user interfaces.
//
// The order of the result is as for types.MethodSet(T).
//
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
isPointerToConcrete := func(T types.Type) bool {
ptr, ok := T.(*types.Pointer)
return ok && !types.IsInterface(ptr.Elem())
}
var result []*types.Selection
mset := msets.MethodSet(T)
if types.IsInterface(T) || isPointerToConcrete(T) {
for i, n := 0, mset.Len(); i < n; i++ {
result = append(result, mset.At(i))
}
} else {
// T is some other concrete type.
// Report methods of T and *T, preferring those of T.
pmset := msets.MethodSet(types.NewPointer(T))
for i, n := 0, pmset.Len(); i < n; i++ {
meth := pmset.At(i)
if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil {
meth = m
}
result = append(result, meth)
}
}
return result
}

View File

@ -10,7 +10,7 @@ import (
)
func CheckRangeStringRunes(j *lint.Job) {
for _, ssafn := range j.Program.InitialFunctions {
for _, ssafn := range j.Pkg.InitialFunctions {
fn := func(node ast.Node) bool {
rng, ok := node.(*ast.RangeStmt)
if !ok || !IsBlank(rng.Key) {

View File

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"io"
"os"
)
var (
@ -15,8 +16,13 @@ var (
crnl = []byte("\r\n")
)
func isGenerated(r io.Reader) bool {
br := bufio.NewReader(r)
func isGenerated(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
br := bufio.NewReader(f)
for {
s, err := br.ReadBytes('\n')
if err != nil && err != io.EOF {

View File

@ -2,6 +2,7 @@
package lint // import "honnef.co/go/tools/lint"
import (
"bytes"
"fmt"
"go/ast"
"go/token"
@ -9,12 +10,14 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"
"unicode"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/config"
"honnef.co/go/tools/ssa"
@ -22,9 +25,9 @@ import (
)
type Job struct {
Program *Program
Pkg *Pkg
GoVersion int
checker string
check Check
problems []Problem
@ -106,20 +109,10 @@ func (gi *GlobIgnore) Match(p Problem) bool {
}
type Program struct {
SSA *ssa.Program
InitialPackages []*Pkg
InitialFunctions []*ssa.Function
AllPackages []*packages.Package
AllFunctions []*ssa.Function
Files []*ast.File
GoVersion int
tokenFileMap map[*token.File]*ast.File
astFileMap map[*ast.File]*Pkg
packagesMap map[string]*packages.Package
genMu sync.RWMutex
generatedMap map[string]bool
SSA *ssa.Program
InitialPackages []*Pkg
AllPackages []*packages.Package
AllFunctions []*ssa.Function
}
func (prog *Program) Fset() *token.FileSet {
@ -141,7 +134,6 @@ type Problem struct {
Position token.Position // position in source file
Text string // the prose that describes the problem
Check string
Checker string
Package *Pkg
Severity Severity
}
@ -164,6 +156,7 @@ type Check struct {
Fn Func
ID string
FilterGenerated bool
Doc string
}
// A Linter lints Go source code.
@ -205,12 +198,8 @@ func (l *Linter) ignore(p Problem) bool {
return false
}
func (prog *Program) File(node Positioner) *ast.File {
return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())]
}
func (j *Job) File(node Positioner) *ast.File {
return j.Program.File(node)
return j.Pkg.tokenFileMap[j.Pkg.Fset.File(node.Pos())]
}
func parseDirective(s string) (cmd string, args []string) {
@ -266,6 +255,7 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
if stats != nil {
stats.SSABuild = time.Since(t)
}
runtime.GC()
t = time.Now()
pkgMap := map[*ssa.Package]*Pkg{}
@ -291,9 +281,19 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
pkg := &Pkg{
SSA: ssapkg,
Package: pkg,
Config: cfg,
SSA: ssapkg,
Package: pkg,
Config: cfg,
Generated: map[string]bool{},
tokenFileMap: map[*token.File]*ast.File{},
}
pkg.Inspector = inspector.New(pkg.Syntax)
for _, f := range pkg.Syntax {
tf := pkg.Fset.File(f.Pos())
pkg.tokenFileMap[tf] = f
path := DisplayPosition(pkg.Fset, f.Pos()).Filename
pkg.Generated[path] = isGenerated(path)
}
pkgMap[ssapkg] = pkg
pkgs = append(pkgs, pkg)
@ -303,42 +303,15 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
SSA: ssaprog,
InitialPackages: pkgs,
AllPackages: allPkgs,
GoVersion: l.GoVersion,
tokenFileMap: map[*token.File]*ast.File{},
astFileMap: map[*ast.File]*Pkg{},
generatedMap: map[string]bool{},
}
prog.packagesMap = map[string]*packages.Package{}
for _, pkg := range allPkgs {
prog.packagesMap[pkg.Types.Path()] = pkg
}
isInitial := map[*types.Package]struct{}{}
for _, pkg := range pkgs {
isInitial[pkg.Types] = struct{}{}
}
for fn := range ssautil.AllFunctions(ssaprog) {
prog.AllFunctions = append(prog.AllFunctions, fn)
if fn.Pkg == nil {
continue
}
prog.AllFunctions = append(prog.AllFunctions, fn)
if _, ok := isInitial[fn.Pkg.Pkg]; ok {
prog.InitialFunctions = append(prog.InitialFunctions, fn)
}
}
for _, pkg := range pkgs {
prog.Files = append(prog.Files, pkg.Syntax...)
ssapkg := ssaprog.Package(pkg.Types)
for _, f := range pkg.Syntax {
prog.astFileMap[f] = pkgMap[ssapkg]
}
}
for _, pkg := range allPkgs {
for _, f := range pkg.Syntax {
tf := pkg.Fset.File(f.Pos())
prog.tokenFileMap[tf] = f
if pkg, ok := pkgMap[fn.Pkg]; ok {
pkg.InitialFunctions = append(pkg.InitialFunctions, fn)
}
}
@ -346,6 +319,19 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
l.automaticIgnores = nil
for _, pkg := range initial {
for _, f := range pkg.Syntax {
found := false
commentLoop:
for _, cg := range f.Comments {
for _, c := range cg.List {
if strings.Contains(c.Text, "//lint:") {
found = true
break commentLoop
}
}
}
if !found {
continue
}
cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
@ -359,10 +345,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
if len(args) < 2 {
// FIXME(dh): this causes duplicated warnings when using megacheck
p := Problem{
Position: prog.DisplayPosition(c.Pos()),
Position: DisplayPosition(prog.Fset(), c.Pos()),
Text: "malformed linter directive; missing the required reason field?",
Check: "",
Checker: "lint",
Package: nil,
}
out = append(out, p)
@ -373,7 +358,7 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
continue
}
checks := strings.Split(args[0], ",")
pos := prog.DisplayPosition(node.Pos())
pos := DisplayPosition(prog.Fset(), node.Pos())
var ig Ignore
switch cmd {
case "ignore":
@ -396,23 +381,6 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
}
sizes := struct {
types int
defs int
uses int
implicits int
selections int
scopes int
}{}
for _, pkg := range pkgs {
sizes.types += len(pkg.TypesInfo.Types)
sizes.defs += len(pkg.TypesInfo.Defs)
sizes.uses += len(pkg.TypesInfo.Uses)
sizes.implicits += len(pkg.TypesInfo.Implicits)
sizes.selections += len(pkg.TypesInfo.Selections)
sizes.scopes += len(pkg.TypesInfo.Scopes)
}
if stats != nil {
stats.OtherInitWork = time.Since(t)
}
@ -428,41 +396,31 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
var jobs []*Job
var allChecks []string
var wg sync.WaitGroup
for _, checker := range l.Checkers {
checks := checker.Checks()
for _, check := range checks {
for _, check := range checker.Checks() {
allChecks = append(allChecks, check.ID)
j := &Job{
Program: prog,
checker: checker.Name(),
check: check,
if check.Fn == nil {
continue
}
for _, pkg := range pkgs {
j := &Job{
Pkg: pkg,
check: check,
GoVersion: l.GoVersion,
}
jobs = append(jobs, j)
wg.Add(1)
go func(check Check, j *Job) {
t := time.Now()
check.Fn(j)
j.duration = time.Since(t)
wg.Done()
}(check, j)
}
jobs = append(jobs, j)
}
}
max := len(jobs)
if l.MaxConcurrentJobs > 0 {
max = l.MaxConcurrentJobs
}
sem := make(chan struct{}, max)
wg := &sync.WaitGroup{}
for _, j := range jobs {
wg.Add(1)
go func(j *Job) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
fn := j.check.Fn
if fn == nil {
return
}
t := time.Now()
fn(j)
j.duration = time.Since(t)
}(j)
}
wg.Wait()
for _, j := range jobs {
@ -470,6 +428,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
stats.Jobs = append(stats.Jobs, JobStat{j.check.ID, j.duration})
}
for _, p := range j.problems {
if p.Package == nil {
panic(fmt.Sprintf("internal error: problem at position %s has nil package", p.Position))
}
allowedChecks := FilterChecks(allChecks, p.Package.Config.Checks)
if l.ignore(p) {
@ -498,19 +459,21 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
}
couldveMatched := false
for f, pkg := range prog.astFileMap {
if prog.Fset().Position(f.Pos()).Filename != ig.File {
continue
}
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
for _, c := range ig.Checks {
if !allowedChecks[c] {
for _, pkg := range pkgs {
for _, f := range pkg.tokenFileMap {
if prog.Fset().Position(f.Pos()).Filename != ig.File {
continue
}
couldveMatched = true
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
for _, c := range ig.Checks {
if !allowedChecks[c] {
continue
}
couldveMatched = true
break
}
break
}
break
}
if !couldveMatched {
@ -519,10 +482,9 @@ func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
continue
}
p := Problem{
Position: prog.DisplayPosition(ig.pos),
Position: DisplayPosition(prog.Fset(), ig.pos),
Text: "this linter directive didn't match anything; should it be removed?",
Check: "",
Checker: "lint",
Package: nil,
}
out = append(out, p)
@ -609,28 +571,30 @@ func FilterChecks(allChecks []string, checks []string) map[string]bool {
return allowedChecks
}
func (prog *Program) Package(path string) *packages.Package {
return prog.packagesMap[path]
}
// Pkg represents a package being linted.
type Pkg struct {
SSA *ssa.Package
SSA *ssa.Package
InitialFunctions []*ssa.Function
*packages.Package
Config config.Config
Config config.Config
Inspector *inspector.Inspector
// TODO(dh): this map should probably map from *ast.File, not string
Generated map[string]bool
tokenFileMap map[*token.File]*ast.File
}
type Positioner interface {
Pos() token.Pos
}
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
// Only use the adjusted position if it points to another Go file.
// This means we'll point to the original file for cgo files, but
// we won't point to a YACC grammar file.
pos := prog.Fset().PositionFor(p, false)
adjPos := prog.Fset().PositionFor(p, true)
pos := fset.PositionFor(p, false)
adjPos := fset.PositionFor(p, true)
if filepath.Ext(adjPos.Filename) == ".go" {
return adjPos
@ -638,60 +602,21 @@ func (prog *Program) DisplayPosition(p token.Pos) token.Position {
return pos
}
func (prog *Program) isGenerated(path string) bool {
// This function isn't very efficient in terms of lock contention
// and lack of parallelism, but it really shouldn't matter.
// Projects consists of thousands of files, and have hundreds of
// errors. That's not a lot of calls to isGenerated.
prog.genMu.RLock()
if b, ok := prog.generatedMap[path]; ok {
prog.genMu.RUnlock()
return b
}
prog.genMu.RUnlock()
prog.genMu.Lock()
defer prog.genMu.Unlock()
// recheck to avoid doing extra work in case of race
if b, ok := prog.generatedMap[path]; ok {
return b
}
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
b := isGenerated(f)
prog.generatedMap[path] = b
return b
}
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
tf := j.Program.SSA.Fset.File(n.Pos())
f := j.Program.tokenFileMap[tf]
pkg := j.Program.astFileMap[f]
pos := j.Program.DisplayPosition(n.Pos())
if j.Program.isGenerated(pos.Filename) && j.check.FilterGenerated {
pos := DisplayPosition(j.Pkg.Fset, n.Pos())
if j.Pkg.Generated[pos.Filename] && j.check.FilterGenerated {
return nil
}
problem := Problem{
Position: pos,
Text: fmt.Sprintf(format, args...),
Check: j.check.ID,
Checker: j.checker,
Package: pkg,
Package: j.Pkg,
}
j.problems = append(j.problems, problem)
return &j.problems[len(j.problems)-1]
}
func (j *Job) NodePackage(node Positioner) *Pkg {
f := j.File(node)
return j.Program.astFileMap[f]
}
func allPackages(pkgs []*packages.Package) []*packages.Package {
var out []*packages.Package
packages.Visit(
@ -704,3 +629,51 @@ func allPackages(pkgs []*packages.Package) []*packages.Package {
)
return out
}
var bufferPool = &sync.Pool{
New: func() interface{} {
buf := bytes.NewBuffer(nil)
buf.Grow(64)
return buf
},
}
func FuncName(f *types.Func) string {
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
if f.Type() != nil {
sig := f.Type().(*types.Signature)
if recv := sig.Recv(); recv != nil {
buf.WriteByte('(')
if _, ok := recv.Type().(*types.Interface); ok {
// gcimporter creates abstract methods of
// named interfaces using the interface type
// (not the named type) as the receiver.
// Don't print it in full.
buf.WriteString("interface")
} else {
types.WriteType(buf, recv.Type(), nil)
}
buf.WriteByte(')')
buf.WriteByte('.')
} else if f.Pkg() != nil {
writePackage(buf, f.Pkg())
}
}
buf.WriteString(f.Name())
s := buf.String()
bufferPool.Put(buf)
return s
}
func writePackage(buf *bytes.Buffer, pkg *types.Package) {
if pkg == nil {
return
}
var s string
s = pkg.Path()
if s != "" {
buf.WriteString(s)
buf.WriteByte('.')
}
}

View File

@ -30,7 +30,7 @@ func CallName(call *ssa.CallCommon) string {
if !ok {
return ""
}
return fn.FullName()
return lint.FuncName(fn)
case *ssa.Builtin:
return v.Name()
}
@ -63,7 +63,7 @@ func IsExample(fn *ssa.Function) bool {
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface, *types.Chan, *types.Map, *types.Pointer:
case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
@ -103,26 +103,14 @@ func IsZero(expr ast.Expr) bool {
return IsIntLiteral(expr, "0")
}
func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
if expr == nil {
return nil
}
return j.NodePackage(expr).TypesInfo.TypeOf(expr)
}
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
if ident == nil {
return nil
}
return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool {
return IsType(j.Pkg.TypesInfo.TypeOf(expr), name)
}
func IsInTest(j *lint.Job, node lint.Positioner) bool {
// FIXME(dh): this doesn't work for global variables with
// initializers
f := j.Program.SSA.Fset.File(node.Pos())
f := j.Pkg.Fset.File(node.Pos())
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
}
@ -130,15 +118,11 @@ func IsInMain(j *lint.Job, node lint.Positioner) bool {
if node, ok := node.(packager); ok {
return node.Package().Pkg.Name() == "main"
}
pkg := j.NodePackage(node)
if pkg == nil {
return false
}
return pkg.Types.Name() == "main"
return j.Pkg.Types.Name() == "main"
}
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
info := j.NodePackage(expr).TypesInfo
info := j.Pkg.TypesInfo
sel := info.Selections[expr]
if sel == nil {
if x, ok := expr.X.(*ast.Ident); ok {
@ -155,11 +139,11 @@ func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
}
func IsNil(j *lint.Job, expr ast.Expr) bool {
return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
return j.Pkg.TypesInfo.Types[expr].IsNil()
}
func BoolConst(j *lint.Job, expr ast.Expr) bool {
val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
val := j.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
return constant.BoolVal(val)
}
@ -172,7 +156,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
if !ok {
return false
}
obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
obj := j.Pkg.TypesInfo.ObjectOf(ident)
c, ok := obj.(*types.Const)
if !ok {
return false
@ -188,7 +172,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
}
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
tv := j.NodePackage(expr).TypesInfo.Types[expr]
tv := j.Pkg.TypesInfo.Types[expr]
if tv.Value == nil {
return 0, false
}
@ -199,7 +183,7 @@ func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
}
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
val := j.NodePackage(expr).TypesInfo.Types[expr].Value
val := j.Pkg.TypesInfo.Types[expr].Value
if val == nil {
return "", false
}
@ -229,22 +213,22 @@ func DereferenceR(T types.Type) types.Type {
}
func IsGoVersion(j *lint.Job, minor int) bool {
return j.Program.GoVersion >= minor
return j.GoVersion >= minor
}
func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
switch fun := call.Fun.(type) {
case *ast.SelectorExpr:
fn, ok := ObjectOf(j, fun.Sel).(*types.Func)
fn, ok := j.Pkg.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
if !ok {
return ""
}
return fn.FullName()
return lint.FuncName(fn)
case *ast.Ident:
obj := ObjectOf(j, fun)
obj := j.Pkg.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return obj.FullName()
return lint.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
@ -273,9 +257,8 @@ func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
}
func Render(j *lint.Job, x interface{}) string {
fset := j.Program.SSA.Fset
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
if err := printer.Fprint(&buf, j.Pkg.Fset, x); err != nil {
panic(err)
}
return buf.String()
@ -311,11 +294,10 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
ast.Inspect(node, fn)
}
func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
if len(specs) == 0 {
return nil
}
fset := j.Program.SSA.Fset
groups := make([][]ast.Spec, 1)
groups[0] = append(groups[0], specs[0])

View File

@ -17,6 +17,7 @@ import (
"os"
"regexp"
"runtime"
"runtime/debug"
"runtime/pprof"
"strconv"
"strings"
@ -109,6 +110,7 @@ func FlagSet(name string) *flag.FlagSet {
flags.Bool("version", false, "Print version and exit")
flags.Bool("show-ignored", false, "Don't filter ignored problems")
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
flags.String("explain", "", "Print description of `check`")
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
flags.Bool("debug.print-stats", false, "Print debug statistics")
@ -131,7 +133,22 @@ func FlagSet(name string) *flag.FlagSet {
return flags
}
func findCheck(cs []lint.Checker, check string) (lint.Check, bool) {
for _, c := range cs {
for _, cc := range c.Checks() {
if cc.ID == check {
return cc, true
}
}
}
return lint.Check{}, false
}
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
if _, ok := os.LookupEnv("GOGC"); !ok {
debug.SetGCPercent(50)
}
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
@ -139,6 +156,7 @@ func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
explain := fs.Lookup("explain").Value.(flag.Getter).Get().(string)
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
@ -175,6 +193,20 @@ func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
exit(0)
}
if explain != "" {
check, ok := findCheck(cs, explain)
if !ok {
fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
exit(1)
}
if check.Doc == "" {
fmt.Fprintln(os.Stderr, explain, "has no documentation")
exit(1)
}
fmt.Println(check.Doc)
exit(0)
}
ps, err := Lint(cs, fs.Args(), &Options{
Tags: strings.Fields(tags),
LintTests: tests,
@ -279,6 +311,7 @@ func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, erro
return nil, err
}
stats.PackageLoading = time.Since(t)
runtime.GC()
var problems []lint.Problem
workingPkgs := make([]*packages.Package, 0, len(pkgs))
@ -346,7 +379,6 @@ func compileErrors(pkg *packages.Package) []lint.Problem {
p := lint.Problem{
Position: parsePos(err.Pos),
Text: err.Msg,
Checker: "compiler",
Check: "compile",
}
ps = append(ps, p)

View File

@ -0,0 +1,11 @@
// +build gofuzz
package printf
func Fuzz(data []byte) int {
_, err := Parse(string(data))
if err == nil {
return 1
}
return 0
}

View File

@ -0,0 +1,197 @@
// Package printf implements a parser for fmt.Printf-style format
// strings.
//
// It parses verbs according to the following syntax:
// Numeric -> '0'-'9'
// Letter -> 'a'-'z' | 'A'-'Z'
// Index -> '[' Numeric+ ']'
// Star -> '*'
// Star -> Index '*'
//
// Precision -> Numeric+ | Star
// Width -> Numeric+ | Star
//
// WidthAndPrecision -> Width '.' Precision
// WidthAndPrecision -> Width '.'
// WidthAndPrecision -> Width
// WidthAndPrecision -> '.' Precision
// WidthAndPrecision -> '.'
//
// Flag -> '+' | '-' | '#' | ' ' | '0'
// Verb -> Letter | '%'
//
// Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
package printf
import (
"errors"
"regexp"
"strconv"
"strings"
)
// ErrInvalid is returned for invalid format strings or verbs.
var ErrInvalid = errors.New("invalid format string")
type Verb struct {
Letter rune
Flags string
Width Argument
Precision Argument
// Which value in the argument list the verb uses.
// -1 denotes the next argument,
// values > 0 denote explicit arguments.
// The value 0 denotes that no argument is consumed. This is the case for %%.
Value int
Raw string
}
// Argument is an implicit or explicit width or precision.
type Argument interface {
isArgument()
}
// The Default value, when no width or precision is provided.
type Default struct{}
// Zero is the implicit zero value.
// This value may only appear for precisions in format strings like %6.f
type Zero struct{}
// Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
type Star struct{ Index int }
// A Literal value, such as 6 in %6d.
type Literal int
func (Default) isArgument() {}
func (Zero) isArgument() {}
func (Star) isArgument() {}
func (Literal) isArgument() {}
// Parse parses f and returns a list of actions.
// An action may either be a literal string, or a Verb.
func Parse(f string) ([]interface{}, error) {
var out []interface{}
for len(f) > 0 {
if f[0] == '%' {
v, n, err := ParseVerb(f)
if err != nil {
return nil, err
}
f = f[n:]
out = append(out, v)
} else {
n := strings.IndexByte(f, '%')
if n > -1 {
out = append(out, f[:n])
f = f[n:]
} else {
out = append(out, f)
f = ""
}
}
}
return out, nil
}
func atoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
// ParseVerb parses the verb at the beginning of f.
// It returns the verb, how much of the input was consumed, and an error, if any.
func ParseVerb(f string) (Verb, int, error) {
if len(f) < 2 {
return Verb{}, 0, ErrInvalid
}
const (
flags = 1
width = 2
widthStar = 3
widthIndex = 5
dot = 6
prec = 7
precStar = 8
precIndex = 10
verbIndex = 11
verb = 12
)
m := re.FindStringSubmatch(f)
if m == nil {
return Verb{}, 0, ErrInvalid
}
v := Verb{
Letter: []rune(m[verb])[0],
Flags: m[flags],
Raw: m[0],
}
if m[width] != "" {
// Literal width
v.Width = Literal(atoi(m[width]))
} else if m[widthStar] != "" {
// Star width
if m[widthIndex] != "" {
v.Width = Star{atoi(m[widthIndex])}
} else {
v.Width = Star{-1}
}
} else {
// Default width
v.Width = Default{}
}
if m[dot] == "" {
// default precision
v.Precision = Default{}
} else {
if m[prec] != "" {
// Literal precision
v.Precision = Literal(atoi(m[prec]))
} else if m[precStar] != "" {
// Star precision
if m[precIndex] != "" {
v.Precision = Star{atoi(m[precIndex])}
} else {
v.Precision = Star{-1}
}
} else {
// Zero precision
v.Precision = Zero{}
}
}
if m[verb] == "%" {
v.Value = 0
} else if m[verbIndex] != "" {
v.Value = atoi(m[verbIndex])
} else {
v.Value = -1
}
return v, len(m[0]), nil
}
const (
flags = `([+#0 -]*)`
verb = `([a-zA-Z%])`
index = `(?:\[([0-9]+)\])`
star = `((` + index + `)?\*)`
width1 = `([0-9]+)`
width2 = star
width = `(?:` + width1 + `|` + width2 + `)`
precision = width
widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
)
var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)

426
vendor/honnef.co/go/tools/simple/doc.go vendored Normal file
View File

@ -0,0 +1,426 @@
package simple
var docS1000 = `Use plain channel send or receive
Select statements with a single case can be replaced with a simple send or receive.
Before:
select {
case x := <-ch:
fmt.Println(x)
}
After:
x := <-ch
fmt.Println(x)
Available since
2017.1
`
var docS1001 = `Replace with copy()
Use copy() for copying elements from one slice to another.
Before:
for i, x := range src {
dst[i] = x
}
After:
copy(dst, src)
Available since
2017.1
`
var docS1002 = `Omit comparison with boolean constant
Before:
if x == true {}
After:
if x {}
Available since
2017.1
`
var docS1003 = `Replace with strings.Contains
Before:
if strings.Index(x, y) != -1 {}
After:
if strings.Contains(x, y) {}
Available since
2017.1
`
var docS1004 = `Replace with bytes.Equal
Before:
if bytes.Compare(x, y) == 0 {}
After:
if bytes.Equal(x, y) {}
Available since
2017.1
`
var docS1005 = `Drop unnecessary use of the blank identifier
In many cases, assigning to the blank identifier is unnecessary.
Before:
for _ = range s {}
x, _ = someMap[key]
_ = <-ch
After:
for range s{}
x = someMap[key]
<-ch
Available since
2017.1
`
var docS1006 = `Replace with for { ... }
For infinite loops, using for { ... } is the most idiomatic choice.
Available since
2017.1
`
var docS1007 = `Simplify regular expression by using raw string literal
Raw string literals use ` + "`" + ` instead of " and do not support any escape sequences. This means that the backslash (\) can be used freely, without the need of escaping.
Since regular expressions have their own escape sequences, raw strings can improve their readability.
Before:
regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
After:
regexp.Compile(` + "`" + `\A(\w+) profile: total \d+\n\z` + "`" + `)
Available since
2017.1
`
var docS1008 = `Simplify returning boolean expression
Before:
if <expr> {
return true
}
return false
After:
return <expr>
Available since
2017.1
`
var docS1009 = `Omit redundant nil check on slices
The len function is defined for all slices, even nil ones, which have a length of zero. It is not necessary to check if a slice is not nil before checking that its length is not zero.
Before:
if x != nil && len(x) != 0 {}
After:
if len(x) != 0 {}
Available since
2017.1
`
var docS1010 = `Omit default slice index
When slicing, the second index defaults to the length of the value, making s[n:len(s)] and s[n:] equivalent.
Available since
2017.1
`
var docS1011 = `Use a single append to concatenate two slices
Before:
for _, e := range y {
x = append(x, e)
}
After:
x = append(x, y...)
Available since
2017.1
`
var docS1012 = `Replace with time.Since(x)
The time.Since helper has the same effect as using time.Now().Sub(x) but is easier to read.
Before:
time.Now().Sub(x)
After:
time.Since(x)
Available since
2017.1
`
var docS1016 = `Use a type conversion
Two struct types with identical fields can be converted between each other. In older versions of Go, the fields had to have identical struct tags. Since Go 1.8, however, struct tags are ignored during conversions. It is thus not necessary to manually copy every field individually.
Before:
var x T1
y := T2{
Field1: x.Field1,
Field2: x.Field2,
}
After:
var x T1
y := T2(x)
Available since
2017.1
`
var docS1017 = `Replace with strings.TrimPrefix
Instead of using strings.HasPrefix and manual slicing, use the strings.TrimPrefix function. If the string doesn't start with the prefix, the original string will be returned. Using strings.TrimPrefix reduces complexity, and avoids common bugs, such as off-by-one mistakes.
Before:
if strings.HasPrefix(str, prefix) {
str = str[len(prefix):]
}
After:
str = strings.TrimPrefix(str, prefix)
Available since
2017.1
`
var docS1018 = `Replace with copy()
copy() permits using the same source and destination slice, even with overlapping ranges. This makes it ideal for sliding elements in a slice.
Before:
for i := 0; i < n; i++ {
bs[i] = bs[offset+i]
}
After:
copy(bs[:n], bs[offset:])
Available since
2017.1
`
var docS1019 = `Simplify make call
The make function has default values for the length and capacity arguments. For channels and maps, the length defaults to zero. Additionally, for slices the capacity defaults to the length.
Available since
2017.1
`
var docS1020 = `Omit redundant nil check in type assertion
Before:
if _, ok := i.(T); ok && i != nil {}
After:
if _, ok := i.(T); ok {}
Available since
2017.1
`
var docS1021 = `Merge variable declaration and assignment
Before:
var x uint
x = 1
After:
var x uint = 1
Available since
2017.1
`
var docS1023 = `Omit redundant control flow
Functions that have no return value do not need a return statement as the final statement of the function.
Switches in Go do not have automatic fallthrough, unlike languages like C. It is not necessary to have a break statement as the final statement in a case block.
Available since
2017.1
`
var docS1024 = `Replace with time.Until(x)
The time.Until helper has the same effect as using x.Sub(time.Now()) but is easier to read.
Before:
x.Sub(time.Now())
After:
time.Until(x)
Available since
2017.1
`
var docS1025 = `Don't use fmt.Sprintf("%s", x) unnecessarily
In many instances, there are easier and more efficient ways of getting a value's string representation. Whenever a value's underlying type is a string already, or the type has a String method, they should be used directly.
Given the following shared definitions
type T1 string
type T2 int
func (T2) String() string { return "Hello, world" }
var x string
var y T1
var z T2
we can simplify the following
fmt.Sprintf("%s", x)
fmt.Sprintf("%s", y)
fmt.Sprintf("%s", z)
to
x
string(y)
z.String()
Available since
2017.1
`
var docS1028 = `replace with fmt.Errorf
Before:
errors.New(fmt.Sprintf(...))
After:
fmt.Errorf(...)
Available since
2017.1
`
var docS1029 = `Range over the string
Ranging over a string will yield byte offsets and runes. If the offset isn't used, this is functionally equivalent to converting the string to a slice of runes and ranging over that. Ranging directly over the string will be more performant, however, as it avoids allocating a new slice, the size of which depends on the length of the string.
Before:
for _, r := range []rune(s) {}
After:
for _, r := range s {}
Available since
2017.1
`
var docS1030 = `Use bytes.Buffer.String or bytes.Buffer.Bytes
bytes.Buffer has both a String and a Bytes method. It is never necessary to use string(buf.Bytes()) or []byte(buf.String()) simply use the other method.
Available since
2017.1
`
var docS1031 = `Omit redundant nil check around loop
You can use range on nil slices and maps, the loop will simply never execute. This makes an additional nil check around the loop unnecessary.
Before:
if s != nil {
for _, x := range s {
...
}
}
After:
for _, x := range s {
...
}
Available since
2017.1
`
var docS1032 = `Replace with sort.Ints(x), sort.Float64s(x), sort.Strings(x)
The sort.Ints, sort.Float64s and sort.Strings functions are easier to read than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x)) and sort.Sort(sort.StringSlice(x)).
Before:
sort.Sort(sort.StringSlice(x))
After:
sort.Strings(x)
Available since
2019.1
`

File diff suppressed because it is too large Load Diff

View File

@ -39,3 +39,20 @@ func Walk(b *ssa.BasicBlock, fn func(*ssa.BasicBlock) bool) {
wl = append(wl, b.Succs...)
}
}
func Vararg(x *ssa.Slice) ([]ssa.Value, bool) {
var out []ssa.Value
slice, ok := x.X.(*ssa.Alloc)
if !ok || slice.Comment != "varargs" {
return nil, false
}
for _, ref := range *slice.Referrers() {
idx, ok := ref.(*ssa.IndexAddr)
if !ok {
continue
}
v := (*idx.Referrers())[0].(*ssa.Store).Val
out = append(out, v)
}
return out, true
}

View File

@ -0,0 +1,797 @@
package staticcheck
var docSA1000 = `Invalid regular expression
Available since
2017.1
`
var docSA1001 = `Invalid template
Available since
2017.1
`
var docSA1002 = `Invalid format in time.Parse
Available since
2017.1
`
var docSA1003 = `Unsupported argument to functions in encoding/binary
The encoding/binary package can only serialize types with known sizes.
This precludes the use of the 'int' and 'uint' types, as their sizes
differ on different architectures. Furthermore, it doesn't support
serializing maps, channels, strings, or functions.
Before Go 1.8, bool wasn't supported, either.
Available since
2017.1
`
var docSA1004 = `Suspiciously small untyped constant in time.Sleep
The time.Sleep function takes a time.Duration as its only argument.
Durations are expressed in nanoseconds. Thus, calling time.Sleep(1)
will sleep for 1 nanosecond. This is a common source of bugs, as sleep
functions in other languages often accept seconds or milliseconds.
The time package provides constants such as time.Second to express
large durations. These can be combined with arithmetic to express
arbitrary durations, for example '5 * time.Second' for 5 seconds.
If you truly meant to sleep for a tiny amount of time, use
'n * time.Nanosecond" to signal to staticcheck that you did mean to sleep
for some amount of nanoseconds.
Available since
2017.1
`
var docSA1005 = `Invalid first argument to exec.Command
os/exec runs programs directly (using variants of the fork and exec
system calls on Unix systems). This shouldn't be confused with running
a command in a shell. The shell will allow for features such as input
redirection, pipes, and general scripting. The shell is also
responsible for splitting the user's input into a program name and its
arguments. For example, the equivalent to
ls / /tmp
would be
exec.Command("ls", "/", "/tmp")
If you want to run a command in a shell, consider using something like
the following but be aware that not all systems, particularly
Windows, will have a /bin/sh program:
exec.Command("/bin/sh", "-c", "ls | grep Awesome")
Available since
2017.1
`
var docSA1006 = `Printf with dynamic first argument and no further arguments
Using fmt.Printf with a dynamic first argument can lead to unexpected
output. The first argument is a format string, where certain character
combinations have special meaning. If, for example, a user were to
enter a string such as
Interest rate: 5%
and you printed it with
fmt.Printf(s)
it would lead to the following output:
Interest rate: 5%!(NOVERB).
Similarly, forming the first parameter via string concatenation with
user input should be avoided for the same reason. When printing user
input, either use a variant of fmt.Print, or use the %s Printf verb
and pass the string as an argument.
Available since
2017.1
`
var docSA1007 = `Invalid URL in net/url.Parse
Available since
2017.1
`
var docSA1008 = `Non-canonical key in http.Header map
Available since
2017.1
`
var docSA1010 = `(*regexp.Regexp).FindAll called with n == 0, which will always return zero results
If n >= 0, the function returns at most n matches/submatches. To
return all results, specify a negative number.
Available since
2017.1
`
var docSA1011 = `Various methods in the strings package expect valid UTF-8, but invalid input is provided
Available since
2017.1
`
var docSA1012 = `A nil context.Context is being passed to a function, consider using context.TODO instead
Available since
2017.1
`
var docSA1013 = `io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second
Available since
2017.1
`
var docSA1014 = `Non-pointer value passed to Unmarshal or Decode
Available since
2017.1
`
var docSA1015 = `Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions
Available since
2017.1
`
var docSA1016 = `Trapping a signal that cannot be trapped
Not all signals can be intercepted by a process. Speficially, on
UNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are
never passed to the process, but instead handled directly by the
kernel. It is therefore pointless to try and handle these signals.
Available since
2017.1
`
var docSA1017 = `Channels used with os/signal.Notify should be buffered
The os/signal package uses non-blocking channel sends when delivering
signals. If the receiving end of the channel isn't ready and the
channel is either unbuffered or full, the signal will be dropped. To
avoid missing signals, the channel should be buffered and of the
appropriate size. For a channel used for notification of just one
signal value, a buffer of size 1 is sufficient.
Available since
2017.1
`
var docSA1018 = `strings.Replace called with n == 0, which does nothing
With n == 0, zero instances will be replaced. To replace all
instances, use a negative number, or use strings.ReplaceAll.
Available since
2017.1
`
var docSA1019 = `Using a deprecated function, variable, constant or field
Available since
2017.1
`
var docSA1020 = `Using an invalid host:port pair with a net.Listen-related function
Available since
2017.1
`
var docSA1021 = `Using bytes.Equal to compare two net.IP
A net.IP stores an IPv4 or IPv6 address as a slice of bytes. The
length of the slice for an IPv4 address, however, can be either 4 or
16 bytes long, using different ways of representing IPv4 addresses. In
order to correctly compare two net.IPs, the net.IP.Equal method should
be used, as it takes both representations into account.
Available since
2017.1
`
var docSA1023 = `Modifying the buffer in an io.Writer implementation
Write must not modify the slice data, even temporarily.
Available since
2017.1
`
var docSA1024 = `A string cutset contains duplicate characters, suggesting TrimPrefix or TrimSuffix should be used instead of TrimLeft or TrimRight
Available since
2017.1
`
var docSA1025 = `It is not possible to use Reset's return value correctly
Available since
2019.1
`
var docSA1026 = `Cannot marshal channels or functions
Available since
Unreleased
`
var docSA1027 = `Atomic access to 64-bit variable must be 64-bit aligned
On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to
arrange for 64-bit alignment of 64-bit words accessed atomically. The
first word in a variable or in an allocated struct, array, or slice
can be relied upon to be 64-bit aligned.
You can use the structlayout tool to inspect the alignment of fields
in a struct.
Available since
Unreleased
`
var docSA2000 = `sync.WaitGroup.Add called inside the goroutine, leading to a race condition
Available since
2017.1
`
var docSA2001 = `Empty critical section, did you mean to defer the unlock?
Available since
2017.1
`
var docSA2002 = `Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed
Available since
2017.1
`
var docSA2003 = `Deferred Lock right after locking, likely meant to defer Unlock instead
Available since
2017.1
`
var docSA3000 = `TestMain doesn't call os.Exit, hiding test failures
Test executables (and in turn 'go test') exit with a non-zero status
code if any tests failed. When specifying your own TestMain function,
it is your responsibility to arrange for this, by calling os.Exit with
the correct code. The correct code is returned by (*testing.M).Run, so
the usual way of implementing TestMain is to end it with
os.Exit(m.Run()).
Available since
2017.1
`
var docSA3001 = `Assigning to b.N in benchmarks distorts the results
The testing package dynamically sets b.N to improve the reliability of
benchmarks and uses it in computations to determine the duration of a
single operation. Benchmark code must not alter b.N as this would
falsify results.
Available since
2017.1
`
var docSA4000 = `Boolean expression has identical expressions on both sides
Available since
2017.1
`
var docSA4001 = `&*x gets simplified to x, it does not copy x
Available since
2017.1
`
var docSA4002 = `Comparing strings with known different sizes has predictable results
Available since
2017.1
`
var docSA4003 = `Comparing unsigned values against negative values is pointless
Available since
2017.1
`
var docSA4004 = `The loop exits unconditionally after one iteration
Available since
2017.1
`
var docSA4005 = `Field assignment that will never be observed. Did you mean to use a pointer receiver?
Available since
2017.1
`
var docSA4006 = `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?
Available since
2017.1
`
var docSA4008 = `The variable in the loop condition never changes, are you incrementing the wrong variable?
Available since
2017.1
`
var docSA4009 = `A function argument is overwritten before its first use
Available since
2017.1
`
var docSA4010 = `The result of append will never be observed anywhere
Available since
2017.1
`
var docSA4011 = `Break statement with no effect. Did you mean to break out of an outer loop?
Available since
2017.1
`
var docSA4012 = `Comparing a value against NaN even though no value is equal to NaN
Available since
2017.1
`
var docSA4013 = `Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.
Available since
2017.1
`
var docSA4014 = `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either
Available since
2017.1
`
var docSA4015 = `Calling functions like math.Ceil on floats converted from integers doesn't do anything useful
Available since
2017.1
`
var docSA4016 = `Certain bitwise operations, such as x ^ 0, do not do anything useful
Available since
2017.1
`
var docSA4017 = `A pure function's return value is discarded, making the call pointless
Available since
2017.1
`
var docSA4018 = `Self-assignment of variables
Available since
2017.1
`
var docSA4019 = `Multiple, identical build constraints in the same file
Available since
2017.1
`
var docSA4020 = `Unreachable case clause in a type switch
In a type switch like the following
type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
var v interface{} = T{}
switch v.(type) {
case io.Reader:
// ...
case T:
// unreachable
}
the second case clause can never be reached because T implements
io.Reader and case clauses are evaluated in source order.
Another example:
type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
func (T) Close() error { return nil }
var v interface{} = T{}
switch v.(type) {
case io.Reader:
// ...
case io.ReadCloser:
// unreachable
}
Even though T has a Close method and thus implements io.ReadCloser,
io.Reader will always match first. The method set of io.Reader is a
subset of io.ReadCloser. Thus it is impossible to match the second
case without mtching the first case.
Structurally equivalent interfaces
A special case of the previous example are structurally identical
interfaces. Given these declarations
type T error
type V error
func doSomething() error {
err, ok := doAnotherThing()
if ok {
return T(err)
}
return U(err)
}
the following type switch will have an unreachable case clause:
switch doSomething().(type) {
case T:
// ...
case V:
// unreachable
}
T will always match before V because they are structurally equivalent
and therefore doSomething()'s return value implements both.
Available since
Unreleased
`
var docSA4021 = `x = append(y) is equivalent to x = y
Available since
Unreleased
`
var docSA5000 = `Assignment to nil map
Available since
2017.1
`
var docSA5001 = `Defering Close before checking for a possible error
Available since
2017.1
`
var docSA5002 = `The empty for loop (for {}) spins and can block the scheduler
Available since
2017.1
`
var docSA5003 = `Defers in infinite loops will never execute
Defers are scoped to the surrounding function, not the surrounding
block. In a function that never returns, i.e. one containing an
infinite loop, defers will never execute.
Available since
2017.1
`
var docSA5004 = `for { select { ... with an empty default branch spins
Available since
2017.1
`
var docSA5005 = `The finalizer references the finalized object, preventing garbage collection
A finalizer is a function associated with an object that runs when the
garbage collector is ready to collect said object, that is when the
object is no longer referenced by anything.
If the finalizer references the object, however, it will always remain
as the final reference to that object, preventing the garbage
collector from collecting the object. The finalizer will never run,
and the object will never be collected, leading to a memory leak. That
is why the finalizer should instead use its first argument to operate
on the object. That way, the number of references can temporarily go
to zero before the object is being passed to the finalizer.
Available since
2017.1
`
var docSA5006 = `Slice index out of bounds
Available since
2017.1
`
var docSA5007 = `Infinite recursive call
A function that calls itself recursively needs to have an exit
condition. Otherwise it will recurse forever, until the system runs
out of memory.
This issue can be caused by simple bugs such as forgetting to add an
exit condition. It can also happen "on purpose". Some languages have
tail call optimization which makes certain infinite recursive calls
safe to use. Go, however, does not implement TCO, and as such a loop
should be used instead.
Available since
2017.1
`
var docSA6000 = `Using regexp.Match or related in a loop, should use regexp.Compile
Available since
2017.1
`
var docSA6001 = `Missing an optimization opportunity when indexing maps by byte slices
Map keys must be comparable, which precludes the use of byte slices.
This usually leads to using string keys and converting byte slices to
strings.
Normally, a conversion of a byte slice to a string needs to copy the data and
causes allocations. The compiler, however, recognizes m[string(b)] and
uses the data of b directly, without copying it, because it knows that
the data can't change during the map lookup. This leads to the
counter-intuitive situation that
k := string(b)
println(m[k])
println(m[k])
will be less efficient than
println(m[string(b)])
println(m[string(b)])
because the first version needs to copy and allocate, while the second
one does not.
For some history on this optimization, check out commit
f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.
Available since
2017.1
`
var docSA6002 = `Storing non-pointer values in sync.Pool allocates memory
A sync.Pool is used to avoid unnecessary allocations and reduce the
amount of work the garbage collector has to do.
When passing a value that is not a pointer to a function that accepts
an interface, the value needs to be placed on the heap, which means an
additional allocation. Slices are a common thing to put in sync.Pools,
and they're structs with 3 fields (length, capacity, and a pointer to
an array). In order to avoid the extra allocation, one should store a
pointer to the slice instead.
See the comments on https://go-review.googlesource.com/c/go/+/24371
that discuss this problem.
Available since
2017.1
`
var docSA6003 = `Converting a string to a slice of runes before ranging over it
You may want to loop over the runes in a string. Instead of converting
the string to a slice of runes and looping over that, you can loop
over the string itself. That is,
for _, r := range s {}
and
for _, r := range []rune(s) {}
will yield the same values. The first version, however, will be faster
and avoid unnecessary memory allocations.
Do note that if you are interested in the indices, ranging over a
string and over a slice of runes will yield different indices. The
first one yields byte offsets, while the second one yields indices in
the slice of runes.
Available since
2017.1
`
var docSA6005 = `Inefficient string comparison with strings.ToLower or strings.ToUpper
Converting two strings to the same case and comparing them like so
if strings.ToLower(s1) == strings.ToLower(s2) {
...
}
is significantly more expensive than comparing them with
strings.EqualFold(s1, s2). This is due to memory usage as well as
computational complexity.
strings.ToLower will have to allocate memory for the new strings, as
well as convert both strings fully, even if they differ on the very
first byte. strings.EqualFold, on the other hand, compares the strings
one character at a time. It doesn't need to create two intermediate
strings and can return as soon as the first non-matching character has
been found.
For a more in-depth explanation of this issue, see
https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/
Available since
Unreleased
`
var docSA9001 = `Defers in 'for range' loops may not run when you expect them to
Available since
2017.1
`
var docSA9002 = `Using a non-octal os.FileMode that looks like it was meant to be in octal.
Available since
2017.1
`
var docSA9003 = `Empty body in an if or else branch
Available since
2017.1
`
var docSA9004 = `Only the first constant has an explicit type
In a constant declaration such as the following:
const (
First byte = 1
Second = 2
)
the constant Second does not have the same type as the constant First.
This construct shouldn't be confused with
const (
First byte = iota
Second
)
where First and Second do indeed have the same type. The type is only
passed on when no explicit value is assigned to the constant.
When declaring enumerations with explicit values it is therefore
important not to write
const (
EnumFirst EnumType = 1
EnumSecond = 2
EnumThird = 3
)
This discrepancy in types can cause various confusing behaviors and
bugs.
Wrong type in variable declarations
The most obvious issue with such incorrect enumerations expresses
itself as a compile error:
package pkg
const (
EnumFirst uint8 = 1
EnumSecond = 2
)
func fn(useFirst bool) {
x := EnumSecond
if useFirst {
x = EnumFirst
}
}
fails to compile with
./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
Losing method sets
A more subtle issue occurs with types that have methods and optional
interfaces. Consider the following:
package main
import "fmt"
type Enum int
func (e Enum) String() string {
return "an enum"
}
const (
EnumFirst Enum = 1
EnumSecond = 2
)
func main() {
fmt.Println(EnumFirst)
fmt.Println(EnumSecond)
}
This code will output
an enum
2
as EnumSecond has no explicit type, and thus defaults to int.
Available since
2019.1
`
var docSA9005 = `Trying to marshal a struct with no public fields nor custom marshaling
The encoding/json and encoding/xml packages only operate on exported
fields in structs, not unexported ones. It is usually an error to try
to (un)marshal structs that only consist of unexported fields.
This check will not flag calls involving types that define custom
marshaling behavior, e.g. via MarshalJSON methods. It will also not
flag empty structs.
Available since
Unreleased
`

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package staticcheck
import "strconv"
func parseStructTag(tag string) (map[string][]string, error) {
// FIXME(dh): detect missing closing quote
out := map[string][]string{}
for tag != "" {
// Skip leading space.
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// Scan to colon. A space, a quote or a control character is a syntax error.
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
// as it is simpler to inspect the tag's bytes than the tag's runes.
i = 0
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
i++
}
if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
break
}
name := string(tag[:i])
tag = tag[i+1:]
// Scan quoted string to find value.
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
break
}
qvalue := string(tag[:i+1])
tag = tag[i+1:]
value, err := strconv.Unquote(qvalue)
if err != nil {
return nil, err
}
out[name] = append(out[name], value)
}
return out, nil
}

View File

@ -12,6 +12,7 @@ import (
"sort"
"strings"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/ssa"
)
@ -291,7 +292,7 @@ func BuildGraph(f *ssa.Function) *Graph {
case *ssa.Call:
if static := ins.Common().StaticCallee(); static != nil {
if fn, ok := static.Object().(*types.Func); ok {
switch fn.FullName() {
switch lint.FuncName(fn) {
case "bytes.Index", "bytes.IndexAny", "bytes.IndexByte",
"bytes.IndexFunc", "bytes.IndexRune", "bytes.LastIndex",
"bytes.LastIndexAny", "bytes.LastIndexByte", "bytes.LastIndexFunc",

View File

@ -0,0 +1,170 @@
package stylecheck
var docST1000 = `Incorrect or missing package comment
Packages must have a package comment that is formatted according to
the guidelines laid out in
https://github.com/golang/go/wiki/CodeReviewComments#package-comments.
Available since
2019.1, non-default
`
var docST1001 = `Dot imports are discouraged
Dot imports that aren't in external test packages are discouraged.
The dot_import_whitelist option can be used to whitelist certain
imports.
Quoting Go Code Review Comments:
The import . form can be useful in tests that, due to circular
dependencies, cannot be made part of the package being tested:
package foo_test
import (
"bar/testutil" // also imports "foo"
. "foo"
)
In this case, the test file cannot be in package foo because it
uses bar/testutil, which imports foo. So we use the 'import .'
form to let the file pretend to be part of package foo even though
it is not. Except for this one case, do not use import . in your
programs. It makes the programs much harder to read because it is
unclear whether a name like Quux is a top-level identifier in the
current package or in an imported package.
Available since
2019.1
Options
dot_import_whitelist
`
var docST1003 = `Poorly chosen identifier
Identifiers, such as variable and package names, follow certain rules.
See the following links for details:
http://golang.org/doc/effective_go.html#package-names
http://golang.org/doc/effective_go.html#mixed-caps
https://github.com/golang/go/wiki/CodeReviewComments#initialisms
https://github.com/golang/go/wiki/CodeReviewComments#variable-names
Available since
2019.1, non-default
Options
initialisms
`
var docST1005 = `Incorrectly formatted error string
Error strings follow a set of guidelines to ensure uniformity and good
composability.
Quoting Go Code Review Comments:
Error strings should not be capitalized (unless beginning with
proper nouns or acronyms) or end with punctuation, since they are
usually printed following other context. That is, use
fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so
that log.Printf("Reading %s: %v", filename, err) formats without a
spurious capital letter mid-message.
Available since
2019.1
`
var docST1006 = `Poorly chosen receiver name
Quoting Go Code Review Comments:
The name of a method's receiver should be a reflection of its
identity; often a one or two letter abbreviation of its type
suffices (such as "c" or "cl" for "Client"). Don't use generic
names such as "me", "this" or "self", identifiers typical of
object-oriented languages that place more emphasis on methods as
opposed to functions. The name need not be as descriptive as that
of a method argument, as its role is obvious and serves no
documentary purpose. It can be very short as it will appear on
almost every line of every method of the type; familiarity admits
brevity. Be consistent, too: if you call the receiver "c" in one
method, don't call it "cl" in another.
Available since
2019.1
`
var docST1008 = `A function's error value should be its last return value
A function's error value should be its last return value.
Available since
2019.1
`
var docST1011 = `Poorly chosen name for variable of type time.Duration
time.Duration values represent an amount of time, which is represented
as a count of nanoseconds. An expression like 5 * time.Microsecond
yields the value 5000. It is therefore not appropriate to suffix a
variable of type time.Duration with any time unit, such as Msec or
Milli.
Available since
2019.1
`
var docST1012 = `Poorly chosen name for error variable
Error variables that are part of an API should be called errFoo or
ErrFoo.
Available since
2019.1
`
var docST1013 = `Should use constants for HTTP error codes, not magic numbers
HTTP has a tremendous number of status codes. While some of those are
well known (200, 400, 404, 500), most of them are not. The net/http
package provides constants for all status codes that are part of the
various specifications. It is recommended to use these constants
instead of hard-coding magic numbers, to vastly improve the
readability of your code.
Available since
2019.1
Options
http_status_code_whitelist
`
var docST1015 = `A switch's default case should be the first or last case
Available since
2019.1
`
var docST1016 = `Use consistent method receiver names
Available since
2019.1, non-default
`
var docST1017 = `Don't use Yoda conditions
Available since
Unreleased
`
var docST1018 = `Avoid zero-width and control characters in string literals
Available since
Unreleased
`

View File

@ -32,23 +32,24 @@ func (c *Checker) Init(prog *lint.Program) {}
func (c *Checker) Checks() []lint.Check {
return []lint.Check{
{ID: "ST1000", FilterGenerated: false, Fn: c.CheckPackageComment},
{ID: "ST1001", FilterGenerated: true, Fn: c.CheckDotImports},
// {ID: "ST1002", FilterGenerated: true, Fn: c.CheckBlankImports},
{ID: "ST1003", FilterGenerated: true, Fn: c.CheckNames},
// {ID: "ST1004", FilterGenerated: false, Fn: nil, },
{ID: "ST1005", FilterGenerated: false, Fn: c.CheckErrorStrings},
{ID: "ST1006", FilterGenerated: false, Fn: c.CheckReceiverNames},
// {ID: "ST1007", FilterGenerated: true, Fn: c.CheckIncDec},
{ID: "ST1008", FilterGenerated: false, Fn: c.CheckErrorReturn},
// {ID: "ST1009", FilterGenerated: false, Fn: c.CheckUnexportedReturn},
// {ID: "ST1010", FilterGenerated: false, Fn: c.CheckContextFirstArg},
{ID: "ST1011", FilterGenerated: false, Fn: c.CheckTimeNames},
{ID: "ST1012", FilterGenerated: false, Fn: c.CheckErrorVarNames},
{ID: "ST1013", FilterGenerated: true, Fn: c.CheckHTTPStatusCodes},
{ID: "ST1015", FilterGenerated: true, Fn: c.CheckDefaultCaseOrder},
{ID: "ST1016", FilterGenerated: false, Fn: c.CheckReceiverNamesIdentical},
{ID: "ST1017", FilterGenerated: true, Fn: c.CheckYodaConditions},
{ID: "ST1000", FilterGenerated: false, Fn: c.CheckPackageComment, Doc: docST1000},
{ID: "ST1001", FilterGenerated: true, Fn: c.CheckDotImports, Doc: docST1001},
// {ID: "ST1002", FilterGenerated: true, Fn: c.CheckBlankImports, Doc: docST1002},
{ID: "ST1003", FilterGenerated: true, Fn: c.CheckNames, Doc: docST1003},
// {ID: "ST1004", FilterGenerated: false, Fn: nil, , Doc: docST1004},
{ID: "ST1005", FilterGenerated: false, Fn: c.CheckErrorStrings, Doc: docST1005},
{ID: "ST1006", FilterGenerated: false, Fn: c.CheckReceiverNames, Doc: docST1006},
// {ID: "ST1007", FilterGenerated: true, Fn: c.CheckIncDec, Doc: docST1007},
{ID: "ST1008", FilterGenerated: false, Fn: c.CheckErrorReturn, Doc: docST1008},
// {ID: "ST1009", FilterGenerated: false, Fn: c.CheckUnexportedReturn, Doc: docST1009},
// {ID: "ST1010", FilterGenerated: false, Fn: c.CheckContextFirstArg, Doc: docST1010},
{ID: "ST1011", FilterGenerated: false, Fn: c.CheckTimeNames, Doc: docST1011},
{ID: "ST1012", FilterGenerated: false, Fn: c.CheckErrorVarNames, Doc: docST1012},
{ID: "ST1013", FilterGenerated: true, Fn: c.CheckHTTPStatusCodes, Doc: docST1013},
{ID: "ST1015", FilterGenerated: true, Fn: c.CheckDefaultCaseOrder, Doc: docST1015},
{ID: "ST1016", FilterGenerated: false, Fn: c.CheckReceiverNamesIdentical, Doc: docST1016},
{ID: "ST1017", FilterGenerated: true, Fn: c.CheckYodaConditions, Doc: docST1017},
{ID: "ST1018", FilterGenerated: false, Fn: c.CheckInvisibleCharacters, Doc: docST1018},
}
}
@ -61,60 +62,56 @@ func (c *Checker) CheckPackageComment(j *lint.Job) {
// which case they get appended. But that doesn't happen a lot in
// the real world.
for _, pkg := range j.Program.InitialPackages {
if pkg.Name == "main" {
if j.Pkg.Name == "main" {
return
}
hasDocs := false
for _, f := range j.Pkg.Syntax {
if IsInTest(j, f) {
continue
}
hasDocs := false
for _, f := range pkg.Syntax {
if f.Doc != nil && len(f.Doc.List) > 0 {
hasDocs = true
prefix := "Package " + f.Name.Name + " "
if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
j.Errorf(f.Doc, `package comment should be of the form "%s..."`, prefix)
}
f.Doc.Text()
}
}
if !hasDocs {
for _, f := range j.Pkg.Syntax {
if IsInTest(j, f) {
continue
}
if f.Doc != nil && len(f.Doc.List) > 0 {
hasDocs = true
prefix := "Package " + f.Name.Name + " "
if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
j.Errorf(f.Doc, `package comment should be of the form "%s..."`, prefix)
}
f.Doc.Text()
}
}
if !hasDocs {
for _, f := range pkg.Syntax {
if IsInTest(j, f) {
continue
}
j.Errorf(f, "at least one file in a package should have a package comment")
}
j.Errorf(f, "at least one file in a package should have a package comment")
}
}
}
func (c *Checker) CheckDotImports(j *lint.Job) {
for _, pkg := range j.Program.InitialPackages {
for _, f := range pkg.Syntax {
imports:
for _, imp := range f.Imports {
path := imp.Path.Value
path = path[1 : len(path)-1]
for _, w := range pkg.Config.DotImportWhitelist {
if w == path {
continue imports
}
for _, f := range j.Pkg.Syntax {
imports:
for _, imp := range f.Imports {
path := imp.Path.Value
path = path[1 : len(path)-1]
for _, w := range j.Pkg.Config.DotImportWhitelist {
if w == path {
continue imports
}
}
if imp.Name != nil && imp.Name.Name == "." && !IsInTest(j, f) {
j.Errorf(imp, "should not use dot imports")
}
if imp.Name != nil && imp.Name.Name == "." && !IsInTest(j, f) {
j.Errorf(imp, "should not use dot imports")
}
}
}
}
func (c *Checker) CheckBlankImports(j *lint.Job) {
fset := j.Program.Fset()
for _, f := range j.Program.Files {
fset := j.Pkg.Fset
for _, f := range j.Pkg.Syntax {
if IsInMain(j, f) || IsInTest(j, f) {
continue
}
@ -177,14 +174,14 @@ func (c *Checker) CheckIncDec(j *lint.Job) {
// x += 2
// ...
// x += 1
fn := func(node ast.Node) bool {
assign, ok := node.(*ast.AssignStmt)
if !ok || (assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN) {
return true
fn := func(node ast.Node) {
assign := node.(*ast.AssignStmt)
if assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN {
return
}
if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
!IsIntLiteral(assign.Rhs[0], "1") {
return true
return
}
suffix := ""
@ -196,16 +193,13 @@ func (c *Checker) CheckIncDec(j *lint.Job) {
}
j.Errorf(assign, "should replace %s with %s%s", Render(j, assign), Render(j, assign.Lhs[0]), suffix)
return true
}
for _, f := range j.Program.Files {
ast.Inspect(f, fn)
}
j.Pkg.Inspector.Preorder([]ast.Node{(*ast.AssignStmt)(nil)}, fn)
}
func (c *Checker) CheckErrorReturn(j *lint.Job) {
fnLoop:
for _, fn := range j.Program.InitialFunctions {
for _, fn := range j.Pkg.InitialFunctions {
sig := fn.Type().(*types.Signature)
rets := sig.Results()
if rets == nil || rets.Len() < 2 {
@ -229,7 +223,7 @@ fnLoop:
// CheckUnexportedReturn checks that exported functions on exported
// types do not return unexported types.
func (c *Checker) CheckUnexportedReturn(j *lint.Job) {
for _, fn := range j.Program.InitialFunctions {
for _, fn := range j.Pkg.InitialFunctions {
if fn.Synthetic != "" || fn.Parent() != nil {
continue
}
@ -252,23 +246,21 @@ func (c *Checker) CheckUnexportedReturn(j *lint.Job) {
}
func (c *Checker) CheckReceiverNames(j *lint.Job) {
for _, pkg := range j.Program.InitialPackages {
for _, m := range pkg.SSA.Members {
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if recv.Name() == "self" || recv.Name() == "this" {
j.Errorf(recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`)
}
if recv.Name() == "_" {
j.Errorf(recv, "receiver name should not be an underscore, omit the name if it is unused")
}
for _, m := range j.Pkg.SSA.Members {
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if recv.Name() == "self" || recv.Name() == "this" {
j.Errorf(recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`)
}
if recv.Name() == "_" {
j.Errorf(recv, "receiver name should not be an underscore, omit the name if it is unused")
}
}
}
@ -276,37 +268,35 @@ func (c *Checker) CheckReceiverNames(j *lint.Job) {
}
func (c *Checker) CheckReceiverNamesIdentical(j *lint.Job) {
for _, pkg := range j.Program.InitialPackages {
for _, m := range pkg.SSA.Members {
names := map[string]int{}
for _, m := range j.Pkg.SSA.Members {
names := map[string]int{}
var firstFn *types.Func
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if firstFn == nil {
firstFn = fn
}
if recv.Name() != "" && recv.Name() != "_" {
names[recv.Name()]++
}
var firstFn *types.Func
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if firstFn == nil {
firstFn = fn
}
if recv.Name() != "" && recv.Name() != "_" {
names[recv.Name()]++
}
}
}
if len(names) > 1 {
var seen []string
for name, count := range names {
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
}
j.Errorf(firstFn, "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))
if len(names) > 1 {
var seen []string
for name, count := range names {
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
}
j.Errorf(firstFn, "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))
}
}
}
@ -315,7 +305,7 @@ func (c *Checker) CheckContextFirstArg(j *lint.Job) {
// TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
// func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
fnLoop:
for _, fn := range j.Program.InitialFunctions {
for _, fn := range j.Pkg.InitialFunctions {
if fn.Synthetic != "" || fn.Parent() != nil {
continue
}
@ -337,17 +327,19 @@ fnLoop:
}
func (c *Checker) CheckErrorStrings(j *lint.Job) {
fnNames := map[*ssa.Package]map[string]bool{}
for _, fn := range j.Program.InitialFunctions {
m := fnNames[fn.Package()]
if m == nil {
m = map[string]bool{}
fnNames[fn.Package()] = m
objNames := map[*ssa.Package]map[string]bool{}
ssapkg := j.Pkg.SSA
objNames[ssapkg] = map[string]bool{}
for _, m := range ssapkg.Members {
if typ, ok := m.(*ssa.Type); ok {
objNames[ssapkg][typ.Name()] = true
}
m[fn.Name()] = true
}
for _, fn := range j.Pkg.InitialFunctions {
objNames[fn.Package()][fn.Name()] = true
}
for _, fn := range j.Program.InitialFunctions {
for _, fn := range j.Pkg.InitialFunctions {
if IsInTest(j, fn) {
// We don't care about malformed error messages in tests;
// they're usually for direct human consumption, not part
@ -399,8 +391,8 @@ func (c *Checker) CheckErrorStrings(j *lint.Job) {
}
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
if fnNames[fn.Package()][word] {
// Word is probably the name of a function in this package
if objNames[fn.Package()][word] {
// Word is probably the name of a function or type in this package
continue
}
// First word in error starts with a capital
@ -437,15 +429,15 @@ func (c *Checker) CheckTimeNames(j *lint.Job) {
}
}
}
for _, f := range j.Program.Files {
for _, f := range j.Pkg.Syntax {
ast.Inspect(f, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.ValueSpec:
T := TypeOf(j, node.Type)
T := j.Pkg.TypesInfo.TypeOf(node.Type)
fn(T, node.Names)
case *ast.FieldList:
for _, field := range node.List {
T := TypeOf(j, field.Type)
T := j.Pkg.TypesInfo.TypeOf(field.Type)
fn(T, field.Names)
}
}
@ -455,7 +447,7 @@ func (c *Checker) CheckTimeNames(j *lint.Job) {
}
func (c *Checker) CheckErrorVarNames(j *lint.Job) {
for _, f := range j.Program.Files {
for _, f := range j.Pkg.Syntax {
for _, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.VAR {
@ -549,61 +541,56 @@ var httpStatusCodes = map[int]string{
}
func (c *Checker) CheckHTTPStatusCodes(j *lint.Job) {
for _, pkg := range j.Program.InitialPackages {
whitelist := map[string]bool{}
for _, code := range pkg.Config.HTTPStatusCodeWhitelist {
whitelist[code] = true
}
fn := func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
var arg int
switch CallNameAST(j, call) {
case "net/http.Error":
arg = 2
case "net/http.Redirect":
arg = 3
case "net/http.StatusText":
arg = 0
case "net/http.RedirectHandler":
arg = 1
default:
return true
}
lit, ok := call.Args[arg].(*ast.BasicLit)
if !ok {
return true
}
if whitelist[lit.Value] {
return true
}
n, err := strconv.Atoi(lit.Value)
if err != nil {
return true
}
s, ok := httpStatusCodes[n]
if !ok {
return true
}
j.Errorf(lit, "should use constant http.%s instead of numeric literal %d", s, n)
whitelist := map[string]bool{}
for _, code := range j.Pkg.Config.HTTPStatusCodeWhitelist {
whitelist[code] = true
}
fn := func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
for _, f := range pkg.Syntax {
ast.Inspect(f, fn)
var arg int
switch CallNameAST(j, call) {
case "net/http.Error":
arg = 2
case "net/http.Redirect":
arg = 3
case "net/http.StatusText":
arg = 0
case "net/http.RedirectHandler":
arg = 1
default:
return true
}
lit, ok := call.Args[arg].(*ast.BasicLit)
if !ok {
return true
}
if whitelist[lit.Value] {
return true
}
n, err := strconv.Atoi(lit.Value)
if err != nil {
return true
}
s, ok := httpStatusCodes[n]
if !ok {
return true
}
j.Errorf(lit, "should use constant http.%s instead of numeric literal %d", s, n)
return true
}
for _, f := range j.Pkg.Syntax {
ast.Inspect(f, fn)
}
}
func (c *Checker) CheckDefaultCaseOrder(j *lint.Job) {
fn := func(node ast.Node) bool {
stmt, ok := node.(*ast.SwitchStmt)
if !ok {
return true
}
fn := func(node ast.Node) {
stmt := node.(*ast.SwitchStmt)
list := stmt.Body.List
for i, c := range list {
if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
@ -611,33 +598,41 @@ func (c *Checker) CheckDefaultCaseOrder(j *lint.Job) {
break
}
}
return true
}
for _, f := range j.Program.Files {
ast.Inspect(f, fn)
}
j.Pkg.Inspector.Preorder([]ast.Node{(*ast.SwitchStmt)(nil)}, fn)
}
func (c *Checker) CheckYodaConditions(j *lint.Job) {
fn := func(node ast.Node) bool {
cond, ok := node.(*ast.BinaryExpr)
if !ok {
return true
}
fn := func(node ast.Node) {
cond := node.(*ast.BinaryExpr)
if cond.Op != token.EQL && cond.Op != token.NEQ {
return true
return
}
if _, ok := cond.X.(*ast.BasicLit); !ok {
return true
return
}
if _, ok := cond.Y.(*ast.BasicLit); ok {
// Don't flag lit == lit conditions, just in case
return true
return
}
j.Errorf(cond, "don't use Yoda conditions")
return true
}
for _, f := range j.Program.Files {
ast.Inspect(f, fn)
}
j.Pkg.Inspector.Preorder([]ast.Node{(*ast.BinaryExpr)(nil)}, fn)
}
func (c *Checker) CheckInvisibleCharacters(j *lint.Job) {
fn := func(node ast.Node) {
lit := node.(*ast.BasicLit)
if lit.Kind != token.STRING {
return
}
for _, r := range lit.Value {
if unicode.Is(unicode.Cf, r) {
j.Errorf(lit, "string literal contains the Unicode format character %U, consider using the %q escape sequence", r, r)
} else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
j.Errorf(lit, "string literal contains the Unicode control character %U, consider using the %q escape sequence", r, r)
}
}
}
j.Pkg.Inspector.Preorder([]ast.Node{(*ast.BasicLit)(nil)}, fn)
}

View File

@ -71,109 +71,107 @@ func (c *Checker) CheckNames(j *lint.Job) {
}
}
for _, pkg := range j.Program.InitialPackages {
initialisms := make(map[string]bool, len(pkg.Config.Initialisms))
for _, word := range pkg.Config.Initialisms {
initialisms[word] = true
initialisms := make(map[string]bool, len(j.Pkg.Config.Initialisms))
for _, word := range j.Pkg.Config.Initialisms {
initialisms[word] = true
}
for _, f := range j.Pkg.Syntax {
// Package names need slightly different handling than other names.
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
j.Errorf(f, "should not use underscores in package names")
}
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name))
}
for _, f := range pkg.Syntax {
// Package names need slightly different handling than other names.
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
j.Errorf(f, "should not use underscores in package names")
}
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name))
}
ast.Inspect(f, func(node ast.Node) bool {
switch v := node.(type) {
case *ast.AssignStmt:
if v.Tok != token.DEFINE {
return true
}
for _, exp := range v.Lhs {
if id, ok := exp.(*ast.Ident); ok {
check(id, "var", initialisms)
}
}
case *ast.FuncDecl:
// Functions with no body are defined elsewhere (in
// assembly, or via go:linkname). These are likely to
// be something very low level (such as the runtime),
// where our rules don't apply.
if v.Body == nil {
return true
ast.Inspect(f, func(node ast.Node) bool {
switch v := node.(type) {
case *ast.AssignStmt:
if v.Tok != token.DEFINE {
return true
}
for _, exp := range v.Lhs {
if id, ok := exp.(*ast.Ident); ok {
check(id, "var", initialisms)
}
}
case *ast.FuncDecl:
// Functions with no body are defined elsewhere (in
// assembly, or via go:linkname). These are likely to
// be something very low level (such as the runtime),
// where our rules don't apply.
if v.Body == nil {
return true
}
if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
return true
}
if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
return true
}
thing := "func"
if v.Recv != nil {
thing = "method"
}
thing := "func"
if v.Recv != nil {
thing = "method"
}
if !isTechnicallyExported(v) {
check(v.Name, thing, initialisms)
}
if !isTechnicallyExported(v) {
check(v.Name, thing, initialisms)
}
checkList(v.Type.Params, thing+" parameter", initialisms)
checkList(v.Type.Results, thing+" result", initialisms)
case *ast.GenDecl:
if v.Tok == token.IMPORT {
return true
}
var thing string
switch v.Tok {
case token.CONST:
thing = "const"
case token.TYPE:
thing = "type"
case token.VAR:
thing = "var"
}
for _, spec := range v.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
check(s.Name, thing, initialisms)
case *ast.ValueSpec:
for _, id := range s.Names {
check(id, thing, initialisms)
}
}
}
case *ast.InterfaceType:
// Do not check interface method names.
// They are often constrainted by the method names of concrete types.
for _, x := range v.Methods.List {
ft, ok := x.Type.(*ast.FuncType)
if !ok { // might be an embedded interface name
continue
}
checkList(ft.Params, "interface method parameter", initialisms)
checkList(ft.Results, "interface method result", initialisms)
}
case *ast.RangeStmt:
if v.Tok == token.ASSIGN {
return true
}
if id, ok := v.Key.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
if id, ok := v.Value.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
case *ast.StructType:
for _, f := range v.Fields.List {
for _, id := range f.Names {
check(id, "struct field", initialisms)
checkList(v.Type.Params, thing+" parameter", initialisms)
checkList(v.Type.Results, thing+" result", initialisms)
case *ast.GenDecl:
if v.Tok == token.IMPORT {
return true
}
var thing string
switch v.Tok {
case token.CONST:
thing = "const"
case token.TYPE:
thing = "type"
case token.VAR:
thing = "var"
}
for _, spec := range v.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
check(s.Name, thing, initialisms)
case *ast.ValueSpec:
for _, id := range s.Names {
check(id, thing, initialisms)
}
}
}
return true
})
}
case *ast.InterfaceType:
// Do not check interface method names.
// They are often constrainted by the method names of concrete types.
for _, x := range v.Methods.List {
ft, ok := x.Type.(*ast.FuncType)
if !ok { // might be an embedded interface name
continue
}
checkList(ft.Params, "interface method parameter", initialisms)
checkList(ft.Results, "interface method result", initialisms)
}
case *ast.RangeStmt:
if v.Tok == token.ASSIGN {
return true
}
if id, ok := v.Key.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
if id, ok := v.Value.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
case *ast.StructType:
for _, f := range v.Fields.List {
for _, id := range f.Names {
check(id, "struct field", initialisms)
}
}
}
return true
})
}
}

View File

@ -37,43 +37,46 @@ func sameId(obj types.Object, pkg *types.Package, name string) bool {
return pkg.Path() == obj.Pkg().Path()
}
func (c *Checker) implements(V types.Type, T *types.Interface) bool {
func (g *Graph) implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*types.Selection, bool) {
// fast path for common case
if T.Empty() {
return true
return nil, true
}
if ityp, _ := V.Underlying().(*types.Interface); ityp != nil {
// TODO(dh): is this code reachable?
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
_, obj := lookupMethod(ityp, m.Pkg(), m.Name())
switch {
case obj == nil:
return false
return nil, false
case !types.Identical(obj.Type(), m.Type()):
return false
return nil, false
}
}
return true
return nil, true
}
// A concrete type implements T if it implements all methods of T.
ms := c.msCache.MethodSet(V)
var sels []*types.Selection
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
sel := ms.Lookup(m.Pkg(), m.Name())
sel := msV.Lookup(m.Pkg(), m.Name())
if sel == nil {
return false
return nil, false
}
f, _ := sel.Obj().(*types.Func)
if f == nil {
return false
return nil, false
}
if !types.Identical(f.Type(), m.Type()) {
return false
return nil, false
}
sels = append(sels, sel)
}
return true
return sels, true
}

File diff suppressed because it is too large Load Diff

5
vendor/modules.txt vendored
View File

@ -180,6 +180,7 @@ golang.org/x/text/unicode/bidi
golang.org/x/tools/go/loader
golang.org/x/tools/go/ast/astutil
golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/ast/inspector
golang.org/x/tools/go/packages
golang.org/x/tools/go/types/typeutil
golang.org/x/tools/go/buildutil
@ -200,7 +201,7 @@ gopkg.in/gomail.v2
gopkg.in/testfixtures.v2
# gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2
# honnef.co/go/tools v0.0.0-20190215041234-466a0476246c
# honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
honnef.co/go/tools/cmd/staticcheck
honnef.co/go/tools/lint
honnef.co/go/tools/lint/lintutil
@ -218,8 +219,10 @@ honnef.co/go/tools/internal/sharedcheck
honnef.co/go/tools/lint/lintdsl
honnef.co/go/tools/deprecated
honnef.co/go/tools/functions
honnef.co/go/tools/printf
honnef.co/go/tools/ssautil
honnef.co/go/tools/staticcheck/vrp
honnef.co/go/tools/go/types/typeutil
honnef.co/go/tools/callgraph
honnef.co/go/tools/callgraph/static
# src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c