diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2021-02-22 20:37:25 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2021-02-22 21:02:12 +0100 |
| commit | fcc6d71be2c3ce7d9305c04fc2e87af554571bac (patch) | |
| tree | b01dbb3d1e2988e28ea158d2d543d603ec0b9569 /vendor/github.com/nishanths | |
| parent | 8f23c528ad5a943b9ffec5dcaf332fd0f614006e (diff) | |
go.mod: update golangci-lint to v1.37
Diffstat (limited to 'vendor/github.com/nishanths')
14 files changed, 542 insertions, 217 deletions
diff --git a/vendor/github.com/nishanths/exhaustive/.gitignore b/vendor/github.com/nishanths/exhaustive/.gitignore index a724b56a9..24bde5301 100644 --- a/vendor/github.com/nishanths/exhaustive/.gitignore +++ b/vendor/github.com/nishanths/exhaustive/.gitignore @@ -1,4 +1,6 @@ .DS_Store +*.swp +tags # binary cmd/exhaustive/exhaustive diff --git a/vendor/github.com/nishanths/exhaustive/.travis.yml b/vendor/github.com/nishanths/exhaustive/.travis.yml new file mode 100644 index 000000000..bd342f558 --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.x + - master + +# Only clone the most recent commit. +git: + depth: 1 + +notifications: + email: false diff --git a/vendor/github.com/nishanths/exhaustive/README.md b/vendor/github.com/nishanths/exhaustive/README.md index ecc76c7c5..90afc87fe 100644 --- a/vendor/github.com/nishanths/exhaustive/README.md +++ b/vendor/github.com/nishanths/exhaustive/README.md @@ -2,6 +2,8 @@ [](https://godoc.org/github.com/nishanths/exhaustive) +[](https://travis-ci.org/nishanths/exhaustive) + The `exhaustive` package and command line program can be used to detect enum switch statements that are not exhaustive. @@ -29,6 +31,8 @@ The command line usage is: Usage: exhaustive [-flags] [packages...] Flags: + -check-generated + check switch statements in generated files also -default-signifies-exhaustive indicates that switch statements are to be considered exhaustive if a 'default' case is present, even if all enum members aren't listed in the switch (default false) @@ -36,8 +40,8 @@ Flags: apply all suggested fixes (default false) Examples: - exhaustive code.org/proj/... - exhaustive -fix example.org/foo/pkg example.org/foo/bar + exhaustive github.com/foo/bar/... + exhaustive github.com/a/b github.com/x/y ``` ## Example @@ -65,11 +69,11 @@ import "token" func processToken(t token.Token) { switch t { case token.Add: - // ... + ... case token.Subtract: - // ... + ... case token.Multiply: - // ... + ... } } ``` @@ -80,6 +84,8 @@ Running the `exhaustive` command will print: calc.go:6:2: missing cases in switch of type token.Token: Quotient, Remainder ``` +Enums can also be defined using explicit constant values instead of `iota`. + ## License BSD 2-Clause diff --git a/vendor/github.com/nishanths/exhaustive/enum.go b/vendor/github.com/nishanths/exhaustive/enum.go index 98b5656b6..ed0df642b 100644 --- a/vendor/github.com/nishanths/exhaustive/enum.go +++ b/vendor/github.com/nishanths/exhaustive/enum.go @@ -8,7 +8,42 @@ import ( "golang.org/x/tools/go/analysis" ) -type enums map[string][]string // enum type name -> enum member names +type enums map[string]*enumMembers // enum type name -> enum members + +type enumMembers struct { + // Names in the order encountered in the AST. + OrderedNames []string + + // Maps name -> (constant.Value).ExactString(). + // If a name is missing in the map, it means that it does not have a + // corresponding constant.Value defined in the AST. + NameToValue map[string]string + + // Maps (constant.Value).ExactString() -> names. + // Names that don't have a constant.Value defined in the AST (e.g., some + // iota constants) will not have a corresponding entry in this map. + ValueToNames map[string][]string +} + +func (em *enumMembers) add(name string, constVal *string) { + em.OrderedNames = append(em.OrderedNames, name) + + if constVal != nil { + if em.NameToValue == nil { + em.NameToValue = make(map[string]string) + } + em.NameToValue[name] = *constVal + + if em.ValueToNames == nil { + em.ValueToNames = make(map[string][]string) + } + em.ValueToNames[*constVal] = append(em.ValueToNames[*constVal], name) + } +} + +func (em *enumMembers) numMembers() int { + return len(em.OrderedNames) +} func findEnums(pass *analysis.Pass) enums { pkgEnums := make(enums) @@ -39,13 +74,14 @@ func findEnums(pass *analysis.Pass) enums { if !ok { continue } + switch i := basic.Info(); { case i&types.IsInteger != 0: - pkgEnums[named.Obj().Name()] = nil + pkgEnums[named.Obj().Name()] = &enumMembers{} case i&types.IsFloat != 0: - pkgEnums[named.Obj().Name()] = nil + pkgEnums[named.Obj().Name()] = &enumMembers{} case i&types.IsString != 0: - pkgEnums[named.Obj().Name()] = nil + pkgEnums[named.Obj().Name()] = &enumMembers{} } } } @@ -64,22 +100,33 @@ func findEnums(pass *analysis.Pass) enums { for _, s := range gen.Specs { // Must be ValueSpec since we've filtered on token.CONST, token.VAR. v := s.(*ast.ValueSpec) - for _, name := range v.Names { + for i, name := range v.Names { obj := pass.TypesInfo.Defs[name] if obj == nil { continue } + named, ok := obj.Type().(*types.Named) if !ok { continue } - members, ok := pkgEnums[named.Obj().Name()] + // Get the constant.Value representation, if any. + var constVal *string + if len(v.Values) > i { + value := v.Values[i] + if con, ok := pass.TypesInfo.Types[value]; ok && con.Value != nil { + str := con.Value.ExactString() // temp var to be able to take address + constVal = &str + } + } + + em, ok := pkgEnums[named.Obj().Name()] if !ok { continue } - members = append(members, obj.Name()) - pkgEnums[named.Obj().Name()] = members + em.add(obj.Name(), constVal) + pkgEnums[named.Obj().Name()] = em } } } @@ -90,7 +137,7 @@ func findEnums(pass *analysis.Pass) enums { // the existence of members. (The type may just be a named type, // for instance.) for k, v := range pkgEnums { - if len(v) == 0 { + if v.numMembers() == 0 { delete(pkgEnums, k) } } diff --git a/vendor/github.com/nishanths/exhaustive/exhaustive.go b/vendor/github.com/nishanths/exhaustive/exhaustive.go index ef869f268..73815a626 100644 --- a/vendor/github.com/nishanths/exhaustive/exhaustive.go +++ b/vendor/github.com/nishanths/exhaustive/exhaustive.go @@ -1,26 +1,27 @@ -// Package exhaustive provides an analyzer that helps ensure enum switch statements -// are exhaustive. The analyzer also provides fixes to make the offending switch -// statements exhaustive (see "Fixes" section). +// Package exhaustive provides an analyzer that checks exhaustiveness of enum +// switch statements. The analyzer also provides fixes to make the offending +// switch statements exhaustive (see "Fixes" section). // // See "cmd/exhaustive" subpackage for the related command line program. // // Definition of enum // -// The language spec does not provide an explicit definition for enums. +// The Go language spec does not provide an explicit definition for enums. // For the purpose of this program, an enum type is a package-level named type // whose underlying type is an integer (includes byte and rune), a float, or // a string type. An enum type must have associated with it one or more // package-level variables of the named type in the package. These variables // constitute the enum's members. // -// In the code snippet below, Biome is an enum type with 3 members. +// In the code snippet below, Biome is an enum type with 3 members. (You may +// also use iota instead of explicitly specifying values.) // // type Biome int // // const ( -// Tundra Biome = iota -// Savanna -// Desert +// Tundra Biome = 1 +// Savanna Biome = 2 +// Desert Biome = 3 // ) // // Switch statement exhaustiveness @@ -36,11 +37,15 @@ // Flags // // The analyzer accepts a boolean flag: -default-signifies-exhaustive. -// The flag, if set, indicates to the analyzer that switch statements +// The flag, if enabled, indicates to the analyzer that switch statements // are to be considered exhaustive as long as a 'default' case is present, even // if all enum members aren't listed in the switch statements cases. // -// The other relevant flag is the -fix flag. +// The -check-generated boolean flag, disabled by default, indicates whether +// to check switch statements in generated Go source files. +// +// The other relevant flag is the -fix flag; its behavior is described +// in the next section. // // Fixes // @@ -48,7 +53,7 @@ // and does not have a 'default' case. The suggested fix always adds a single // case clause for the missing enum members. // -// case missingA, missingB, missingC: +// case MissingA, MissingB, MissingC: // panic(fmt.Sprintf("unhandled value: %v", v)) // // where v is the expression in the switch statement's tag (in other words, the @@ -63,7 +68,7 @@ // // Imports will be adjusted automatically to account for the "fmt" dependency. // -// Skip analysis of specific switch statements +// Skipping analysis // // If the following directive comment: // @@ -71,6 +76,10 @@ // // is associated with a switch statement, the analyzer skips // checking of the switch statement and no diagnostics are reported. +// +// Additionally, no diagnostics are reported for switch statements in +// generated files (see https://golang.org/s/generatedcode for definition of +// generated file), unless the -check-generated flag is enabled. package exhaustive import ( @@ -84,20 +93,21 @@ import ( "golang.org/x/tools/go/ast/inspector" ) +// Flag names used by the analyzer. They are exported for use by analyzer +// driver programs. const ( - // DefaultSignifiesExhaustiveFlag is a flag name used by the analyzer. It - // is exported for use by analyzer driver programs. DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive" + CheckGeneratedFlag = "check-generated" ) var ( - fCheckMaps bool fDefaultSignifiesExhaustive bool + fCheckGeneratedFiles bool ) func init() { - Analyzer.Flags.BoolVar(&fCheckMaps, "maps", false, "check key exhaustiveness for map literals of enum key type, in addition to checking switch statements") Analyzer.Flags.BoolVar(&fDefaultSignifiesExhaustive, DefaultSignifiesExhaustiveFlag, false, "indicates that switch statements are to be considered exhaustive if a 'default' case is present, even if all enum members aren't listed in the switch") + Analyzer.Flags.BoolVar(&fCheckGeneratedFiles, CheckGeneratedFlag, false, "check switch statements in generated files also") } var Analyzer = &analysis.Analyzer{ @@ -109,8 +119,7 @@ var Analyzer = &analysis.Analyzer{ } // IgnoreDirectivePrefix is used to exclude checking of specific switch statements. -// See https://godoc.org/github.com/nishanths/exhaustive#hdr-Skip_analysis_of_specific_switch_statements -// for details. +// See package comment for details. const IgnoreDirectivePrefix = "//exhaustive:ignore" func containsIgnoreDirective(comments []*ast.Comment) bool { @@ -123,7 +132,7 @@ func containsIgnoreDirective(comments []*ast.Comment) bool { } type enumsFact struct { - Entries enums + Enums enums } var _ analysis.Fact = (*enumsFact)(nil) @@ -133,20 +142,21 @@ func (e *enumsFact) AFact() {} func (e *enumsFact) String() string { // sort for stability (required for testing) var sortedKeys []string - for k := range e.Entries { + for k := range e.Enums { sortedKeys = append(sortedKeys, k) } sort.Strings(sortedKeys) var buf strings.Builder for i, k := range sortedKeys { - v := e.Entries[k] + v := e.Enums[k] buf.WriteString(k) buf.WriteString(":") - for j, vv := range v { + + for j, vv := range v.OrderedNames { buf.WriteString(vv) // add comma separator between each enum member in an enum type - if j != len(v)-1 { + if j != len(v.OrderedNames)-1 { buf.WriteString(",") } } @@ -161,16 +171,14 @@ func (e *enumsFact) String() string { func run(pass *analysis.Pass) (interface{}, error) { e := findEnums(pass) if len(e) != 0 { - pass.ExportPackageFact(&enumsFact{Entries: e}) + pass.ExportPackageFact(&enumsFact{Enums: e}) } inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) comments := make(map[*ast.File]ast.CommentMap) // CommentMap per package file, lazily populated by reference + generated := make(map[*ast.File]bool) - checkSwitchStatements(pass, inspect, comments) - if fCheckMaps { - checkMapLiterals(pass, inspect, comments) - } + checkSwitchStatements(pass, inspect, comments, generated) return nil, nil } diff --git a/vendor/github.com/nishanths/exhaustive/generated.go b/vendor/github.com/nishanths/exhaustive/generated.go new file mode 100644 index 000000000..19b4fb12b --- /dev/null +++ b/vendor/github.com/nishanths/exhaustive/generated.go @@ -0,0 +1,34 @@ +package exhaustive + +import ( + "go/ast" + "strings" +) + +// Adapated from https://gotools.org/dmitri.shuralyov.com/go/generated + +func isGeneratedFile(file *ast.File) bool { + for _, c := range file.Comments { + for _, cc := range c.List { + s := cc.Text // "\n" already removed (see doc comment) + if len(s) >= 1 && s[len(s)-1] == '\r' { + s = s[:len(s)-1] // Trim "\r". + } + if containsGeneratedComment(s) { + return true + } + } + } + + return false +} + +func containsGeneratedComment(s string) bool { + return strings.HasPrefix(s, genCommentPrefix) && + strings.HasSuffix(s, genCommentSuffix) +} + +const ( + genCommentPrefix = "// Code generated " + genCommentSuffix = " DO NOT EDIT." +) diff --git a/vendor/github.com/nishanths/exhaustive/go.mod b/vendor/github.com/nishanths/exhaustive/go.mod index b15048eab..9a75e5152 100644 --- a/vendor/github.com/nishanths/exhaustive/go.mod +++ b/vendor/github.com/nishanths/exhaustive/go.mod @@ -2,4 +2,4 @@ module github.com/nishanths/exhaustive go 1.14 -require golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a +require golang.org/x/tools v0.0.0-20201011145850-ed2f50202694 diff --git a/vendor/github.com/nishanths/exhaustive/go.sum b/vendor/github.com/nishanths/exhaustive/go.sum index 01ba99965..4f00a79cc 100644 --- a/vendor/github.com/nishanths/exhaustive/go.sum +++ b/vendor/github.com/nishanths/exhaustive/go.sum @@ -1,21 +1,38 @@ +github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a h1:gILuVKC+ZPD6g/tj6zBOdnOH1ZHI0zZ86+KLMogc6/s= golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200519142718-10921354bc51 h1:GtYAC9y+dpwWCXBwbcZgxcFfiqW4SI93yvQqpF+9+P8= +golang.org/x/tools v0.0.0-20201011145850-ed2f50202694 h1:BANdcOVw3KTuUiyfDp7wrzCpkCe8UP3lowugJngxBTg= +golang.org/x/tools v0.0.0-20201011145850-ed2f50202694/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/nishanths/exhaustive/map.go b/vendor/github.com/nishanths/exhaustive/map.go deleted file mode 100644 index 6d875e32a..000000000 --- a/vendor/github.com/nishanths/exhaustive/map.go +++ /dev/null @@ -1,158 +0,0 @@ -package exhaustive - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "sort" - "strings" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/ast/inspector" -) - -func checkMapLiterals(pass *analysis.Pass, inspect *inspector.Inspector, comments map[*ast.File]ast.CommentMap) { - for _, f := range pass.Files { - for _, d := range f.Decls { - gen, ok := d.(*ast.GenDecl) - if !ok { - continue - } - if gen.Tok != token.VAR { - continue // map literals have to be declared as "var" - } - for _, s := range gen.Specs { - valueSpec := s.(*ast.ValueSpec) - for idx, name := range valueSpec.Names { - obj := pass.TypesInfo.Defs[name] - if obj == nil { - continue - } - - mapType, ok := obj.Type().(*types.Map) - if !ok { - continue - } - - keyType, ok := mapType.Key().(*types.Named) - if !ok { - continue - } - keyPkg := keyType.Obj().Pkg() - if keyPkg == nil { - // Doc comment: nil for labels and objects in the Universe scope. - // This happens for the `error` type, for example. - // Continuing would mean that ImportPackageFact panics. - continue - } - - var enums enumsFact - if !pass.ImportPackageFact(keyPkg, &enums) { - // Can't do anything further. - continue - } - - enumMembers, ok := enums.Entries[keyType.Obj().Name()] - if !ok { - // Key type is not a known enum. - continue - } - - // Check comments for the ignore directive. - - var allComments ast.CommentMap - if cm, ok := comments[f]; ok { - allComments = cm - } else { - allComments = ast.NewCommentMap(pass.Fset, f, f.Comments) - comments[f] = allComments - } - - genDeclComments := allComments.Filter(gen) - genDeclIgnore := false - for _, group := range genDeclComments.Comments() { - if containsIgnoreDirective(group.List) && gen.Lparen == token.NoPos && len(gen.Specs) == 1 { - genDeclIgnore = true - break - } - } - if genDeclIgnore { - continue - } - - if (valueSpec.Doc != nil && containsIgnoreDirective(valueSpec.Doc.List)) || - (valueSpec.Comment != nil && containsIgnoreDirective(valueSpec.Comment.List)) { - continue - } - - samePkg := keyPkg == pass.Pkg - checkUnexported := samePkg - - hitlist := hitlistFromEnumMembers(enumMembers, checkUnexported) - if len(hitlist) == 0 { - // can happen if external package and enum consists only of - // unexported members - continue - } - - if !(len(valueSpec.Values) > idx) { - continue // no value for name - } - comp, ok := valueSpec.Values[idx].(*ast.CompositeLit) - if !ok { - continue - } - for _, el := range comp.Elts { - kvExpr, ok := el.(*ast.KeyValueExpr) - if !ok { - continue - } - e := astutil.Unparen(kvExpr.Key) - if samePkg { - ident, ok := e.(*ast.Ident) - if !ok { - continue - } - delete(hitlist, ident.Name) - } else { - selExpr, ok := e.(*ast.SelectorExpr) - if !ok { - continue - } - - // ensure X is package identifier - ident, ok := selExpr.X.(*ast.Ident) - if !ok { - continue - } - if !isPackageNameIdentifier(pass, ident) { - continue - } - - delete(hitlist, selExpr.Sel.Name) - } - } - - if len(hitlist) > 0 { - reportMapLiteral(pass, name, samePkg, keyType, hitlist) - } - } - } - } - } -} - -func reportMapLiteral(pass *analysis.Pass, mapVarIdent *ast.Ident, samePkg bool, enumType *types.Named, missingMembers map[string]struct{}) { - missing := make([]string, 0, len(missingMembers)) - for m := range missingMembers { - missing = append(missing, m) - } - sort.Strings(missing) - - pass.Report(analysis.Diagnostic{ - Pos: mapVarIdent.Pos(), - Message: fmt.Sprintf("missing keys in map %s of key type %s: %s", mapVarIdent.Name, enumTypeName(enumType, samePkg), strings.Join(missing, ", ")), - }) -} diff --git a/vendor/github.com/nishanths/exhaustive/switch.go b/vendor/github.com/nishanths/exhaustive/switch.go index 5889c2934..2cec7f9cb 100644 --- a/vendor/github.com/nishanths/exhaustive/switch.go +++ b/vendor/github.com/nishanths/exhaustive/switch.go @@ -20,11 +20,33 @@ func isDefaultCase(c *ast.CaseClause) bool { return c.List == nil // see doc comment on field } -func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, comments map[*ast.File]ast.CommentMap) { +func checkSwitchStatements( + pass *analysis.Pass, + inspect *inspector.Inspector, + comments map[*ast.File]ast.CommentMap, + generated map[*ast.File]bool, +) { inspect.WithStack([]ast.Node{&ast.SwitchStmt{}}, func(n ast.Node, push bool, stack []ast.Node) bool { if !push { return true } + + file := stack[0].(*ast.File) + + // Determine if file is a generated file, based on https://golang.org/s/generatedcode. + // If generated, don't check this file. + var isGenerated bool + if gen, ok := generated[file]; ok { + isGenerated = gen + } else { + isGenerated = isGeneratedFile(file) + generated[file] = isGenerated + } + if isGenerated && !fCheckGeneratedFiles { + // don't check + return true + } + sw := n.(*ast.SwitchStmt) if sw.Tag == nil { return true @@ -52,14 +74,13 @@ func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, co return true } - enumMembers, isEnum := enums.Entries[tagType.Obj().Name()] + em, isEnum := enums.Enums[tagType.Obj().Name()] if !isEnum { // Tag's type is not a known enum. return true } // Get comment map. - file := stack[0].(*ast.File) var allComments ast.CommentMap if cm, ok := comments[file]; ok { allComments = cm @@ -78,7 +99,7 @@ func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, co samePkg := tagPkg == pass.Pkg checkUnexported := samePkg - hitlist := hitlistFromEnumMembers(enumMembers, checkUnexported) + hitlist := hitlistFromEnumMembers(em, checkUnexported) if len(hitlist) == 0 { // can happen if external package and enum consists only of // unexported members @@ -99,7 +120,7 @@ func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, co if !ok { continue } - delete(hitlist, ident.Name) + updateHitlist(hitlist, em, ident.Name) } else { selExpr, ok := e.(*ast.SelectorExpr) if !ok { @@ -115,7 +136,7 @@ func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, co continue } - delete(hitlist, selExpr.Sel.Name) + updateHitlist(hitlist, em, selExpr.Sel.Name) } } } @@ -124,12 +145,27 @@ func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, co shouldReport := len(hitlist) > 0 && !defaultSuffices if shouldReport { - reportSwitch(pass, sw, samePkg, tagType, hitlist, defaultCaseExists, file) + reportSwitch(pass, sw, samePkg, tagType, em, hitlist, defaultCaseExists, file) } return true }) } +func updateHitlist(hitlist map[string]struct{}, em *enumMembers, foundName string) { + constVal, ok := em.NameToValue[foundName] + if !ok { + // only delete the name alone from hitlist + delete(hitlist, foundName) + return + } + + // delete all of the same-valued names from hitlist + namesToDelete := em.ValueToNames[constVal] + for _, n := range namesToDelete { + delete(hitlist, n) + } +} + func isPackageNameIdentifier(pass *analysis.Pass, ident *ast.Ident) bool { obj := pass.TypesInfo.ObjectOf(ident) if obj == nil { @@ -139,26 +175,54 @@ func isPackageNameIdentifier(pass *analysis.Pass, ident *ast.Ident) bool { return ok } -func hitlistFromEnumMembers(enumMembers []string, checkUnexported bool) map[string]struct{} { +func hitlistFromEnumMembers(em *enumMembers, checkUnexported bool) map[string]struct{} { hitlist := make(map[string]struct{}) - for _, m := range enumMembers { + for _, m := range em.OrderedNames { if m == "_" { // blank identifier is often used to skip entries in iota lists continue } - if ast.IsExported(m) || checkUnexported { - hitlist[m] = struct{}{} + if !ast.IsExported(m) && !checkUnexported { + continue } + hitlist[m] = struct{}{} } return hitlist } -func reportSwitch(pass *analysis.Pass, sw *ast.SwitchStmt, samePkg bool, enumType *types.Named, missingMembers map[string]struct{}, defaultCaseExists bool, f *ast.File) { - missing := make([]string, 0, len(missingMembers)) +func determineMissingOutput(missingMembers map[string]struct{}, em *enumMembers) []string { + constValMembers := make(map[string][]string) // value -> names + var otherMembers []string // non-constant value names + for m := range missingMembers { - missing = append(missing, m) + if constVal, ok := em.NameToValue[m]; ok { + constValMembers[constVal] = append(constValMembers[constVal], m) + } else { + otherMembers = append(otherMembers, m) + } } - sort.Strings(missing) + + missingOutput := make([]string, 0, len(constValMembers)+len(otherMembers)) + for _, names := range constValMembers { + sort.Strings(names) + missingOutput = append(missingOutput, strings.Join(names, "|")) + } + missingOutput = append(missingOutput, otherMembers...) + sort.Strings(missingOutput) + return missingOutput +} + +func reportSwitch( + pass *analysis.Pass, + sw *ast.SwitchStmt, + samePkg bool, + enumType *types.Named, + em *enumMembers, + missingMembers map[string]struct{}, + defaultCaseExists bool, + f *ast.File, +) { + missingOutput := determineMissingOutput(missingMembers, em) var fixes []analysis.SuggestedFix if !defaultCaseExists { @@ -170,7 +234,7 @@ func reportSwitch(pass *analysis.Pass, sw *ast.SwitchStmt, samePkg bool, enumTyp pass.Report(analysis.Diagnostic{ Pos: sw.Pos(), End: sw.End(), - Message: fmt.Sprintf("missing cases in switch of type %s: %s", enumTypeName(enumType, samePkg), strings.Join(missing, ", ")), + Message: fmt.Sprintf("missing cases in switch of type %s: %s", enumTypeName(enumType, samePkg), strings.Join(missingOutput, ", ")), SuggestedFixes: fixes, }) } diff --git a/vendor/github.com/nishanths/predeclared/LICENSE b/vendor/github.com/nishanths/predeclared/LICENSE new file mode 100644 index 000000000..946212315 --- /dev/null +++ b/vendor/github.com/nishanths/predeclared/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Nishanth Shanmugham +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/nishanths/predeclared/passes/predeclared/go18.go b/vendor/github.com/nishanths/predeclared/passes/predeclared/go18.go new file mode 100644 index 000000000..4083efc74 --- /dev/null +++ b/vendor/github.com/nishanths/predeclared/passes/predeclared/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package predeclared + +import "go/doc" + +func isPredeclaredIdent(name string) bool { + return doc.IsPredeclared(name) +} diff --git a/vendor/github.com/nishanths/predeclared/passes/predeclared/pre_go18.go b/vendor/github.com/nishanths/predeclared/passes/predeclared/pre_go18.go new file mode 100644 index 000000000..5780e0b56 --- /dev/null +++ b/vendor/github.com/nishanths/predeclared/passes/predeclared/pre_go18.go @@ -0,0 +1,53 @@ +// +build !go1.8 + +package predeclared + +func isPredeclaredIdent(name string) bool { + return predeclaredIdents[name] +} + +// Keep in sync with https://golang.org/ref/spec#Predeclared_identifiers +var predeclaredIdents = map[string]bool{ + "bool": true, + "byte": true, + "complex64": true, + "complex128": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int8": true, + "int16": true, + "int32": true, + "int64": true, + "rune": true, + "string": true, + "uint": true, + "uint8": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uintptr": true, + + "true": true, + "false": true, + "iota": true, + + "nil": true, + + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true, + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, +} diff --git a/vendor/github.com/nishanths/predeclared/passes/predeclared/predeclared.go b/vendor/github.com/nishanths/predeclared/passes/predeclared/predeclared.go new file mode 100644 index 000000000..67c0e0a00 --- /dev/null +++ b/vendor/github.com/nishanths/predeclared/passes/predeclared/predeclared.go @@ -0,0 +1,202 @@ +// Package predeclared provides a static analysis (used by the predeclared command) +// that can detect declarations in Go code that shadow one of Go's predeclared identifiers. +package predeclared + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" +) + +// Flag names used by the analyzer. They are exported for use by analyzer +// driver programs. +const ( + IgnoreFlag = "ignore" + QualifiedFlag = "q" +) + +var ( + fIgnore string + fQualified bool +) + +func init() { + Analyzer.Flags.StringVar(&fIgnore, IgnoreFlag, "", "comma-separated list of predeclared identifiers to not report on") + Analyzer.Flags.BoolVar(&fQualified, QualifiedFlag, false, "include method names and field names (i.e., qualified names) in checks") +} + +var Analyzer = &analysis.Analyzer{ + Name: "predeclared", + Doc: "find code that shadows one of Go's predeclared identifiers", + Run: run, +} + +func run(pass *analysis.Pass) (interface{}, error) { + cfg := newConfig(fIgnore, fQualified) + for _, file := range pass.Files { + processFile(pass.Report, cfg, pass.Fset, file) + } + return nil, nil +} + +type config struct { + qualified bool + ignoredIdents map[string]struct{} +} + +func newConfig(ignore string, qualified bool) *config { + cfg := &config{ + qualified: qualified, + ignoredIdents: map[string]struct{}{}, + } + for _, s := range strings.Split(ignore, ",") { + ident := strings.TrimSpace(s) + if ident == "" { + continue + } + cfg.ignoredIdents[ident] = struct{}{} + } + return cfg +} + +type issue struct { + ident *ast.Ident + kind string + fset *token.FileSet +} + +func (i issue) String() string { + pos := i.fset.Position(i.ident.Pos()) + return fmt.Sprintf("%s: %s %s has same name as predeclared identifier", pos, i.kind, i.ident.Name) +} + +func processFile(report func(analysis.Diagnostic), cfg *config, fset *token.FileSet, file *ast.File) []issue { // nolint: gocyclo + var issues []issue + + maybeReport := func(x *ast.Ident, kind string) { + if _, isIgnored := cfg.ignoredIdents[x.Name]; !isIgnored && isPredeclaredIdent(x.Name) { + report(analysis.Diagnostic{ + Pos: x.Pos(), + End: x.End(), + Message: fmt.Sprintf("%s %s has same name as predeclared identifier", kind, x.Name), + }) + issues = append(issues, issue{x, kind, fset}) + } + } + + seenValueSpecs := make(map[*ast.ValueSpec]bool) + + // TODO: consider deduping package name issues for files in the + // same directory. + maybeReport(file.Name, "package name") + + for _, spec := range file.Imports { + if spec.Name == nil { + continue + } + maybeReport(spec.Name, "import name") + } + + // Handle declarations and fields. + // https://golang.org/ref/spec#Declarations_and_scope + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.GenDecl: + var kind string + switch x.Tok { + case token.CONST: + kind = "const" + case token.VAR: + kind = "variable" + default: + return true + } + for _, spec := range x.Specs { + if vspec, ok := spec.(*ast.ValueSpec); ok && !seenValueSpecs[vspec] { + seenValueSpecs[vspec] = true + for _, name := range vspec.Names { + maybeReport(name, kind) + } + } + } + return true + case *ast.TypeSpec: + maybeReport(x.Name, "type") + return true + case *ast.StructType: + if cfg.qualified && x.Fields != nil { + for _, field := range x.Fields.List { + for _, name := range field.Names { + maybeReport(name, "field") + } + } + } + return true + case *ast.InterfaceType: + if cfg.qualified && x.Methods != nil { + for _, meth := range x.Methods.List { + for _, name := range meth.Names { + maybeReport(name, "method") + } + } + } + return true + case *ast.FuncDecl: + if x.Recv == nil { + // it's a function + maybeReport(x.Name, "function") + } else { + // it's a method + if cfg.qualified { + maybeReport(x.Name, "method") + } + } + // add receivers idents + if x.Recv != nil { + for _, field := range x.Recv.List { + for _, name := range field.Names { + maybeReport(name, "receiver") + } + } + } + // Params and Results will be checked in the *ast.FuncType case. + return true + case *ast.FuncType: + // add params idents + for _, field := range x.Params.List { + for _, name := range field.Names { + maybeReport(name, "param") + } + } + // add returns idents + if x.Results != nil { + for _, field := range x.Results.List { + for _, name := range field.Names { + maybeReport(name, "named return") + } + } + } + return true + case *ast.LabeledStmt: + maybeReport(x.Label, "label") + return true + case *ast.AssignStmt: + // We only care about short variable declarations, which use token.DEFINE. + if x.Tok == token.DEFINE { + for _, expr := range x.Lhs { + if ident, ok := expr.(*ast.Ident); ok { + maybeReport(ident, "variable") + } + } + } + return true + default: + return true + } + }) + + return issues +} |
