aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/nishanths
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-09-15 18:05:35 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-09-15 19:34:30 +0200
commit712de1c63d9db97c81af68cd0dc4372c53d2e57a (patch)
treeae1761fec52c3ae4ddd003a4130ddbda8d0a2d69 /vendor/github.com/nishanths
parent298a69c38dd5c8a9bbd7a022e88f4ddbcf885e16 (diff)
vendor/github.com/golangci/golangci-lint: update to v1.31
Diffstat (limited to 'vendor/github.com/nishanths')
-rw-r--r--vendor/github.com/nishanths/exhaustive/.gitignore5
-rw-r--r--vendor/github.com/nishanths/exhaustive/LICENSE25
-rw-r--r--vendor/github.com/nishanths/exhaustive/README.md85
-rw-r--r--vendor/github.com/nishanths/exhaustive/enum.go99
-rw-r--r--vendor/github.com/nishanths/exhaustive/exhaustive.go182
-rw-r--r--vendor/github.com/nishanths/exhaustive/go.mod5
-rw-r--r--vendor/github.com/nishanths/exhaustive/go.sum21
-rw-r--r--vendor/github.com/nishanths/exhaustive/map.go158
-rw-r--r--vendor/github.com/nishanths/exhaustive/switch.go367
9 files changed, 947 insertions, 0 deletions
diff --git a/vendor/github.com/nishanths/exhaustive/.gitignore b/vendor/github.com/nishanths/exhaustive/.gitignore
new file mode 100644
index 000000000..a724b56a9
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+
+# binary
+cmd/exhaustive/exhaustive
+exhaustive
diff --git a/vendor/github.com/nishanths/exhaustive/LICENSE b/vendor/github.com/nishanths/exhaustive/LICENSE
new file mode 100644
index 000000000..32befa68f
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2020, 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:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. 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.
+
+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/exhaustive/README.md b/vendor/github.com/nishanths/exhaustive/README.md
new file mode 100644
index 000000000..ecc76c7c5
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/README.md
@@ -0,0 +1,85 @@
+# exhaustive
+
+[![Godoc](https://godoc.org/github.com/nishanths/exhaustive?status.svg)](https://godoc.org/github.com/nishanths/exhaustive)
+
+The `exhaustive` package and command line program can be used to detect
+enum switch statements that are not exhaustive.
+
+An enum switch statement is exhaustive if it has cases for each of the enum's members. See godoc for the definition of enum used by the program.
+
+The `exhaustive` package provides an `Analyzer` that follows the guidelines
+described in the [go/analysis](https://godoc.org/golang.org/x/tools/go/analysis) package; this makes
+it possible to integrate into existing analysis driver programs.
+
+## Install
+
+```
+go get github.com/nishanths/exhaustive/...
+```
+
+## Docs
+
+https://godoc.org/github.com/nishanths/exhaustive
+
+## Usage
+
+The command line usage is:
+
+```
+Usage: exhaustive [-flags] [packages...]
+
+Flags:
+ -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)
+ -fix
+ apply all suggested fixes (default false)
+
+Examples:
+ exhaustive code.org/proj/...
+ exhaustive -fix example.org/foo/pkg example.org/foo/bar
+```
+
+## Example
+
+Given the code:
+
+```diff
+package token
+
+type Token int
+
+const (
+ Add Token = iota
+ Subtract
+ Multiply
++ Quotient
++ Remainder
+)
+```
+```
+package calc
+
+import "token"
+
+func processToken(t token.Token) {
+ switch t {
+ case token.Add:
+ // ...
+ case token.Subtract:
+ // ...
+ case token.Multiply:
+ // ...
+ }
+}
+```
+
+Running the `exhaustive` command will print:
+
+```
+calc.go:6:2: missing cases in switch of type token.Token: Quotient, Remainder
+```
+
+## License
+
+BSD 2-Clause
diff --git a/vendor/github.com/nishanths/exhaustive/enum.go b/vendor/github.com/nishanths/exhaustive/enum.go
new file mode 100644
index 000000000..98b5656b6
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/enum.go
@@ -0,0 +1,99 @@
+package exhaustive
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+type enums map[string][]string // enum type name -> enum member names
+
+func findEnums(pass *analysis.Pass) enums {
+ pkgEnums := make(enums)
+
+ // Gather enum types.
+ for _, f := range pass.Files {
+ for _, decl := range f.Decls {
+ gen, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ if gen.Tok != token.TYPE {
+ continue
+ }
+ for _, s := range gen.Specs {
+ // Must be TypeSpec since we've filtered on token.TYPE.
+ t, ok := s.(*ast.TypeSpec)
+ obj := pass.TypesInfo.Defs[t.Name]
+ if obj == nil {
+ continue
+ }
+
+ named, ok := obj.Type().(*types.Named)
+ if !ok {
+ continue
+ }
+ basic, ok := named.Underlying().(*types.Basic)
+ if !ok {
+ continue
+ }
+ switch i := basic.Info(); {
+ case i&types.IsInteger != 0:
+ pkgEnums[named.Obj().Name()] = nil
+ case i&types.IsFloat != 0:
+ pkgEnums[named.Obj().Name()] = nil
+ case i&types.IsString != 0:
+ pkgEnums[named.Obj().Name()] = nil
+ }
+ }
+ }
+ }
+
+ // Gather enum members.
+ for _, f := range pass.Files {
+ for _, decl := range f.Decls {
+ gen, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ if gen.Tok != token.CONST && gen.Tok != token.VAR {
+ continue
+ }
+ 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 {
+ obj := pass.TypesInfo.Defs[name]
+ if obj == nil {
+ continue
+ }
+ named, ok := obj.Type().(*types.Named)
+ if !ok {
+ continue
+ }
+
+ members, ok := pkgEnums[named.Obj().Name()]
+ if !ok {
+ continue
+ }
+ members = append(members, obj.Name())
+ pkgEnums[named.Obj().Name()] = members
+ }
+ }
+ }
+ }
+
+ // Delete member-less enum types.
+ // We can't call these enums, since we can't be sure without
+ // the existence of members. (The type may just be a named type,
+ // for instance.)
+ for k, v := range pkgEnums {
+ if len(v) == 0 {
+ delete(pkgEnums, k)
+ }
+ }
+
+ return pkgEnums
+}
diff --git a/vendor/github.com/nishanths/exhaustive/exhaustive.go b/vendor/github.com/nishanths/exhaustive/exhaustive.go
new file mode 100644
index 000000000..ef869f268
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/exhaustive.go
@@ -0,0 +1,182 @@
+// 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).
+//
+// See "cmd/exhaustive" subpackage for the related command line program.
+//
+// Definition of enum
+//
+// The 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.
+//
+// type Biome int
+//
+// const (
+// Tundra Biome = iota
+// Savanna
+// Desert
+// )
+//
+// Switch statement exhaustiveness
+//
+// An enum switch statement is exhaustive if it has cases for each of the enum's members.
+//
+// For an enum type defined in the same package as the switch statement, both
+// exported and unexported enum members must be present in order to consider
+// the switch exhaustive. On the other hand, for an enum type defined
+// in an external package it is sufficient for just exported enum members
+// to be present in order to consider the switch exhaustive.
+//
+// Flags
+//
+// The analyzer accepts a boolean flag: -default-signifies-exhaustive.
+// The flag, if set, 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.
+//
+// Fixes
+//
+// The analyzer suggests fixes for a switch statement if it is not exhaustive
+// 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:
+// panic(fmt.Sprintf("unhandled value: %v", v))
+//
+// where v is the expression in the switch statement's tag (in other words, the
+// value being switched upon). If the switch statement's tag is a function or a
+// method call the analyzer does not suggest a fix, as reusing the call expression
+// in the panic/fmt.Sprintf call could be mutative.
+//
+// The rationale for the fix using panic is that it might be better to fail loudly on
+// existing unhandled or impossible cases than to let them slip by quietly unnoticed.
+// An even better fix may, of course, be to manually inspect the sites reported
+// by the package and handle the missing cases if necessary.
+//
+// Imports will be adjusted automatically to account for the "fmt" dependency.
+//
+// Skip analysis of specific switch statements
+//
+// If the following directive comment:
+//
+// //exhaustive:ignore
+//
+// is associated with a switch statement, the analyzer skips
+// checking of the switch statement and no diagnostics are reported.
+package exhaustive
+
+import (
+ "go/ast"
+ "go/types"
+ "sort"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+const (
+ // DefaultSignifiesExhaustiveFlag is a flag name used by the analyzer. It
+ // is exported for use by analyzer driver programs.
+ DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive"
+)
+
+var (
+ fCheckMaps bool
+ fDefaultSignifiesExhaustive 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")
+}
+
+var Analyzer = &analysis.Analyzer{
+ Name: "exhaustive",
+ Doc: "check exhaustiveness of enum switch statements",
+ Run: run,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ FactTypes: []analysis.Fact{&enumsFact{}},
+}
+
+// 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.
+const IgnoreDirectivePrefix = "//exhaustive:ignore"
+
+func containsIgnoreDirective(comments []*ast.Comment) bool {
+ for _, c := range comments {
+ if strings.HasPrefix(c.Text, IgnoreDirectivePrefix) {
+ return true
+ }
+ }
+ return false
+}
+
+type enumsFact struct {
+ Entries enums
+}
+
+var _ analysis.Fact = (*enumsFact)(nil)
+
+func (e *enumsFact) AFact() {}
+
+func (e *enumsFact) String() string {
+ // sort for stability (required for testing)
+ var sortedKeys []string
+ for k := range e.Entries {
+ sortedKeys = append(sortedKeys, k)
+ }
+ sort.Strings(sortedKeys)
+
+ var buf strings.Builder
+ for i, k := range sortedKeys {
+ v := e.Entries[k]
+ buf.WriteString(k)
+ buf.WriteString(":")
+ for j, vv := range v {
+ buf.WriteString(vv)
+ // add comma separator between each enum member in an enum type
+ if j != len(v)-1 {
+ buf.WriteString(",")
+ }
+ }
+ // add semicolon separator between each enum type
+ if i != len(sortedKeys)-1 {
+ buf.WriteString("; ")
+ }
+ }
+ return buf.String()
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ e := findEnums(pass)
+ if len(e) != 0 {
+ pass.ExportPackageFact(&enumsFact{Entries: e})
+ }
+
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ comments := make(map[*ast.File]ast.CommentMap) // CommentMap per package file, lazily populated by reference
+
+ checkSwitchStatements(pass, inspect, comments)
+ if fCheckMaps {
+ checkMapLiterals(pass, inspect, comments)
+ }
+ return nil, nil
+}
+
+func enumTypeName(e *types.Named, samePkg bool) string {
+ if samePkg {
+ return e.Obj().Name()
+ }
+ return e.Obj().Pkg().Name() + "." + e.Obj().Name()
+}
diff --git a/vendor/github.com/nishanths/exhaustive/go.mod b/vendor/github.com/nishanths/exhaustive/go.mod
new file mode 100644
index 000000000..b15048eab
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/go.mod
@@ -0,0 +1,5 @@
+module github.com/nishanths/exhaustive
+
+go 1.14
+
+require golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a
diff --git a/vendor/github.com/nishanths/exhaustive/go.sum b/vendor/github.com/nishanths/exhaustive/go.sum
new file mode 100644
index 000000000..01ba99965
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/go.sum
@@ -0,0 +1,21 @@
+github.com/yuin/goldmark v1.1.27/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/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
+golang.org/x/mod v0.2.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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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/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/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=
diff --git a/vendor/github.com/nishanths/exhaustive/map.go b/vendor/github.com/nishanths/exhaustive/map.go
new file mode 100644
index 000000000..6d875e32a
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/map.go
@@ -0,0 +1,158 @@
+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
new file mode 100644
index 000000000..5889c2934
--- /dev/null
+++ b/vendor/github.com/nishanths/exhaustive/switch.go
@@ -0,0 +1,367 @@
+package exhaustive
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "go/token"
+ "go/types"
+ "sort"
+ "strconv"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+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) {
+ inspect.WithStack([]ast.Node{&ast.SwitchStmt{}}, func(n ast.Node, push bool, stack []ast.Node) bool {
+ if !push {
+ return true
+ }
+ sw := n.(*ast.SwitchStmt)
+ if sw.Tag == nil {
+ return true
+ }
+ t := pass.TypesInfo.Types[sw.Tag]
+ if !t.IsValue() {
+ return true
+ }
+ tagType, ok := t.Type.(*types.Named)
+ if !ok {
+ return true
+ }
+
+ tagPkg := tagType.Obj().Pkg()
+ if tagPkg == 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.
+ return true
+ }
+
+ var enums enumsFact
+ if !pass.ImportPackageFact(tagPkg, &enums) {
+ // Can't do anything further.
+ return true
+ }
+
+ enumMembers, isEnum := enums.Entries[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
+ } else {
+ allComments = ast.NewCommentMap(pass.Fset, file, file.Comments)
+ comments[file] = allComments
+ }
+
+ specificComments := allComments.Filter(sw)
+ for _, group := range specificComments.Comments() {
+ if containsIgnoreDirective(group.List) {
+ return true // skip checking due to ignore directive
+ }
+ }
+
+ samePkg := tagPkg == pass.Pkg
+ checkUnexported := samePkg
+
+ hitlist := hitlistFromEnumMembers(enumMembers, checkUnexported)
+ if len(hitlist) == 0 {
+ // can happen if external package and enum consists only of
+ // unexported members
+ return true
+ }
+
+ defaultCaseExists := false
+ for _, stmt := range sw.Body.List {
+ caseCl := stmt.(*ast.CaseClause)
+ if isDefaultCase(caseCl) {
+ defaultCaseExists = true
+ continue // nothing more to do if it's the default case
+ }
+ for _, e := range caseCl.List {
+ e = astutil.Unparen(e)
+ 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)
+ }
+ }
+ }
+
+ defaultSuffices := fDefaultSignifiesExhaustive && defaultCaseExists
+ shouldReport := len(hitlist) > 0 && !defaultSuffices
+
+ if shouldReport {
+ reportSwitch(pass, sw, samePkg, tagType, hitlist, defaultCaseExists, file)
+ }
+ return true
+ })
+}
+
+func isPackageNameIdentifier(pass *analysis.Pass, ident *ast.Ident) bool {
+ obj := pass.TypesInfo.ObjectOf(ident)
+ if obj == nil {
+ return false
+ }
+ _, ok := obj.(*types.PkgName)
+ return ok
+}
+
+func hitlistFromEnumMembers(enumMembers []string, checkUnexported bool) map[string]struct{} {
+ hitlist := make(map[string]struct{})
+ for _, m := range enumMembers {
+ if m == "_" {
+ // blank identifier is often used to skip entries in iota lists
+ continue
+ }
+ if ast.IsExported(m) || checkUnexported {
+ 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))
+ for m := range missingMembers {
+ missing = append(missing, m)
+ }
+ sort.Strings(missing)
+
+ var fixes []analysis.SuggestedFix
+ if !defaultCaseExists {
+ if fix, ok := computeFix(pass, pass.Fset, f, sw, enumType, samePkg, missingMembers); ok {
+ fixes = append(fixes, fix)
+ }
+ }
+
+ 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, ", ")),
+ SuggestedFixes: fixes,
+ })
+}
+
+func computeFix(pass *analysis.Pass, fset *token.FileSet, f *ast.File, sw *ast.SwitchStmt, enumType *types.Named, samePkg bool, missingMembers map[string]struct{}) (analysis.SuggestedFix, bool) {
+ // Function and method calls may be mutative, so we don't want to reuse the
+ // call expression in the about-to-be-inserted case clause body. So we just
+ // don't suggest a fix in such situations.
+ //
+ // However, we need to make an exception for type conversions, which are
+ // also call expressions in the AST.
+ //
+ // We'll need to lookup type information for this, and can't rely solely
+ // on the AST.
+ if containsFuncCall(pass, sw.Tag) {
+ return analysis.SuggestedFix{}, false
+ }
+
+ textEdits := []analysis.TextEdit{
+ missingCasesTextEdit(fset, f, samePkg, sw, enumType, missingMembers),
+ }
+
+ // need to add "fmt" import if "fmt" import doesn't already exist
+ if !hasImportWithPath(fset, f, `"fmt"`) {
+ textEdits = append(textEdits, fmtImportTextEdit(fset, f))
+ }
+
+ missing := make([]string, 0, len(missingMembers))
+ for m := range missingMembers {
+ missing = append(missing, m)
+ }
+ sort.Strings(missing)
+
+ return analysis.SuggestedFix{
+ Message: fmt.Sprintf("add case clause for: %s?", strings.Join(missing, ", ")),
+ TextEdits: textEdits,
+ }, true
+}
+
+func containsFuncCall(pass *analysis.Pass, e ast.Expr) bool {
+ e = astutil.Unparen(e)
+ c, ok := e.(*ast.CallExpr)
+ if !ok {
+ return false
+ }
+ if _, isFunc := pass.TypesInfo.TypeOf(c.Fun).Underlying().(*types.Signature); isFunc {
+ return true
+ }
+ for _, a := range c.Args {
+ if containsFuncCall(pass, a) {
+ return true
+ }
+ }
+ return false
+}
+
+func firstImportDecl(fset *token.FileSet, f *ast.File) *ast.GenDecl {
+ for _, decl := range f.Decls {
+ genDecl, ok := decl.(*ast.GenDecl)
+ if ok && genDecl.Tok == token.IMPORT {
+ // first IMPORT GenDecl
+ return genDecl
+ }
+ }
+ return nil
+}
+
+// copies an GenDecl in a manner such that appending to the returned GenDecl's Specs field
+// doesn't mutate the original GenDecl
+func copyGenDecl(im *ast.GenDecl) *ast.GenDecl {
+ imCopy := *im
+ imCopy.Specs = make([]ast.Spec, len(im.Specs))
+ for i := range im.Specs {
+ imCopy.Specs[i] = im.Specs[i]
+ }
+ return &imCopy
+}
+
+func hasImportWithPath(fset *token.FileSet, f *ast.File, pathLiteral string) bool {
+ igroups := astutil.Imports(fset, f)
+ for _, igroup := range igroups {
+ for _, importSpec := range igroup {
+ if importSpec.Path.Value == pathLiteral {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func fmtImportTextEdit(fset *token.FileSet, f *ast.File) analysis.TextEdit {
+ firstDecl := firstImportDecl(fset, f)
+
+ if firstDecl == nil {
+ // file has no import declarations
+ // insert "fmt" import spec after package statement
+ return analysis.TextEdit{
+ Pos: f.Name.End() + 1, // end of package name + 1
+ End: f.Name.End() + 1,
+ NewText: []byte(`import (
+ "fmt"
+ )`),
+ }
+ }
+
+ // copy because we'll be mutating its Specs field
+ firstDeclCopy := copyGenDecl(firstDecl)
+
+ // find insertion index for "fmt" import spec
+ var i int
+ for ; i < len(firstDeclCopy.Specs); i++ {
+ im := firstDeclCopy.Specs[i].(*ast.ImportSpec)
+ if v, _ := strconv.Unquote(im.Path.Value); v > "fmt" {
+ break
+ }
+ }
+
+ // insert "fmt" import spec at the index
+ fmtSpec := &ast.ImportSpec{
+ Path: &ast.BasicLit{
+ // NOTE: Pos field doesn't seem to be required for our
+ // purposes here.
+ Kind: token.STRING,
+ Value: `"fmt"`,
+ },
+ }
+ s := firstDeclCopy.Specs // local var for easier comprehension of next line
+ s = append(s[:i], append([]ast.Spec{fmtSpec}, s[i:]...)...)
+ firstDeclCopy.Specs = s
+
+ // create the text edit
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, firstDeclCopy)
+
+ return analysis.TextEdit{
+ Pos: firstDecl.Pos(),
+ End: firstDecl.End(),
+ NewText: buf.Bytes(),
+ }
+}
+
+func missingCasesTextEdit(fset *token.FileSet, f *ast.File, samePkg bool, sw *ast.SwitchStmt, enumType *types.Named, missingMembers map[string]struct{}) analysis.TextEdit {
+ // ... Construct insertion text for case clause and its body ...
+
+ var tag bytes.Buffer
+ printer.Fprint(&tag, fset, sw.Tag)
+
+ // If possible and if necessary, determine the package identifier based on the AST of other `case` clauses.
+ var pkgIdent *ast.Ident
+ if !samePkg {
+ for _, stmt := range sw.Body.List {
+ caseCl := stmt.(*ast.CaseClause)
+ // At least one expression must exist in List at this point.
+ // List cannot be nil because we only arrive here if the "default" clause
+ // does not exist. Additionally, a syntactically valid case clause must
+ // have at least one expression.
+ if sel, ok := caseCl.List[0].(*ast.SelectorExpr); ok {
+ pkgIdent = sel.X.(*ast.Ident)
+ break
+ }
+ }
+ }
+
+ missing := make([]string, 0, len(missingMembers))
+ for m := range missingMembers {
+ if !samePkg {
+ if pkgIdent != nil {
+ // we were able to determine package identifier
+ missing = append(missing, pkgIdent.Name+"."+m)
+ } else {
+ // use the package name (may not be correct always)
+ //
+ // TODO: May need to also add import if the package isn't imported
+ // elsewhere. This (ie, a switch with zero case clauses) should
+ // happen rarely, so don't implement this for now.
+ missing = append(missing, enumType.Obj().Pkg().Name()+"."+m)
+ }
+ } else {
+ missing = append(missing, m)
+ }
+ }
+ sort.Strings(missing)
+
+ insert := `case ` + strings.Join(missing, ", ") + `:
+ panic(fmt.Sprintf("unhandled value: %v",` + tag.String() + `))`
+
+ // ... Create the text edit ...
+
+ return analysis.TextEdit{
+ Pos: sw.Body.Rbrace - 1,
+ End: sw.Body.Rbrace - 1,
+ NewText: []byte(insert),
+ }
+}