diff --git a/README.md b/README.md index 3992704..a65d9de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## exhaustive [![Godoc][2]][1] -Check exhaustiveness of enum switch statements in Go source code. +Check exhaustiveness of enum switch statements and map literals in Go source code. ``` go install github.com/nishanths/exhaustive/cmd/exhaustive@latest @@ -39,7 +39,7 @@ const ( ) ``` -and the switch statement +and the code ```go package calc @@ -54,12 +54,21 @@ func f(t token.Token) { default: } } + +func g(t token.Token) string { + return map[token.Token]string{ + token.Add: "add", + token.Subtract: "subtract", + token.Multiply: "multiply", + }[t] +} ``` running exhaustive will print ``` calc.go:6:2: missing cases in switch of type token.Token: Quotient, Remainder +calc.go:15:9: missing map keys of type token.Token: Quotient, Remainder ``` ## Contributing diff --git a/comment.go b/comment.go index cae8c64..1232df1 100644 --- a/comment.go +++ b/comment.go @@ -2,6 +2,7 @@ package exhaustive import ( "go/ast" + "go/token" "regexp" "strings" ) @@ -50,10 +51,28 @@ func isGeneratedFileComment(s string) bool { return generatedCodeRe.MatchString(s) } +type generatedCache map[*ast.File]bool + +func (c generatedCache) IsGenerated(file *ast.File) bool { + if _, ok := c[file]; !ok { + c[file] = isGeneratedFile(file) + } + return c[file] +} + // ignoreDirective is used to exclude checking of specific switch statements. const ignoreDirective = "//exhaustive:ignore" const enforceDirective = "//exhaustive:enforce" +type commentsCache map[*ast.File]ast.CommentMap + +func (c commentsCache) GetComments(file *ast.File, set *token.FileSet) ast.CommentMap { + if _, ok := c[file]; !ok { + c[file] = ast.NewCommentMap(set, file, file.Comments) + } + return c[file] +} + func containsDirective(comments []*ast.CommentGroup, directive string) bool { for _, c := range comments { for _, cc := range c.List { diff --git a/exhaustive.go b/exhaustive.go index e852df7..d1d1523 100644 --- a/exhaustive.go +++ b/exhaustive.go @@ -1,6 +1,6 @@ /* Package exhaustive provides an analyzer that checks exhaustiveness of enum -switch statements in Go source code. +switch statements and map literals in Go source code. Definition of enum @@ -52,6 +52,11 @@ The analyzer will produce a diagnostic about unhandled enum members if the required memebers are not listed in a switch statement's cases (this applies even if the switch statement has a 'default' case). +Map literals + +All above relates to map literals as well, if key is an enum type. +But empty map is ignored because it's an alternative for make(map...). + Type aliases The analyzer handles type aliases for an enum type in the following manner. @@ -115,6 +120,7 @@ All of these flags are optional. flag type default value -explicit-exhaustive-switch bool false + -explicit-exhaustive-map bool false -check-generated bool false -default-signifies-exhaustive bool false -ignore-enum-members string (none) @@ -126,6 +132,11 @@ switch statements explicitly marked with the comment text ("exhaustive:enforce"). Otherwise, it runs on every enum switch statement not marked with the comment text ("exhaustive:ignore"). +If the -explicit-exhaustive-map flag is enabled, the analyzer only runs on +map literals explicitly marked with the comment text +("exhaustive:enforce"). Otherwise, it runs on every enum map literal not +marked with the comment text ("exhaustive:ignore"). + If the -check-generated flag is enabled, switch statements in generated Go source files are also checked. Otherwise, by default, switch statements in generated files are not checked. See https://golang.org/s/generatedcode for the @@ -176,6 +187,7 @@ package exhaustive import ( "flag" + "go/ast" "regexp" "golang.org/x/tools/go/analysis" @@ -215,6 +227,7 @@ func (v *regexpFlag) value() *regexp.Regexp { return v.r } func init() { Analyzer.Flags.BoolVar(&fExplicitExhaustiveSwitch, ExplicitExhaustiveSwitchFlag, false, "only run exhaustive check on switches with \"//exhaustive:enforce\" comment") + Analyzer.Flags.BoolVar(&fExplicitExhaustiveMap, ExplicitExhaustiveMapFlag, false, "only run exhaustive check on map literals with \"//exhaustive:enforce\" comment") Analyzer.Flags.BoolVar(&fCheckGenerated, CheckGeneratedFlag, false, "check switch statements in generated files") Analyzer.Flags.BoolVar(&fDefaultSignifiesExhaustive, DefaultSignifiesExhaustiveFlag, false, "presence of \"default\" case in switch statements satisfies exhaustiveness, even if all enum members are not listed") Analyzer.Flags.Var(&fIgnoreEnumMembers, IgnoreEnumMembersFlag, "enum members matching `regex` do not have to be listed in switch statements to satisfy exhaustiveness") @@ -229,6 +242,7 @@ func init() { // driver programs. const ( ExplicitExhaustiveSwitchFlag = "explicit-exhaustive-switch" + ExplicitExhaustiveMapFlag = "explicit-exhaustive-map" CheckGeneratedFlag = "check-generated" DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive" IgnoreEnumMembersFlag = "ignore-enum-members" @@ -240,6 +254,7 @@ const ( var ( fExplicitExhaustiveSwitch bool + fExplicitExhaustiveMap bool fCheckGenerated bool fDefaultSignifiesExhaustive bool fIgnoreEnumMembers regexpFlag @@ -250,6 +265,7 @@ var ( // Useful in tests. func resetFlags() { fExplicitExhaustiveSwitch = false + fExplicitExhaustiveMap = false fCheckGenerated = false fDefaultSignifiesExhaustive = false fIgnoreEnumMembers = regexpFlag{} @@ -258,7 +274,7 @@ func resetFlags() { var Analyzer = &analysis.Analyzer{ Name: "exhaustive", - Doc: "check exhaustiveness of enum switch statements", + Doc: "check exhaustiveness of enum switch statements and map literals", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, FactTypes: []analysis.Fact{&enumMembersFact{}}, @@ -271,11 +287,47 @@ func run(pass *analysis.Pass) (interface{}, error) { exportFact(pass, typ, members) } - checkSwitchStatements(pass, inspect, config{ - explicitExhaustiveSwitch: fExplicitExhaustiveSwitch, - defaultSignifiesExhaustive: fDefaultSignifiesExhaustive, - checkGeneratedFiles: fCheckGenerated, - ignoreEnumMembers: fIgnoreEnumMembers.value(), + generated := make(generatedCache) + comments := make(commentsCache) + + checkSwitch := switchStmtChecker( + pass, + switchConfig{ + explicitExhaustiveSwitch: fExplicitExhaustiveSwitch, + defaultSignifiesExhaustive: fDefaultSignifiesExhaustive, + checkGeneratedFiles: fCheckGenerated, + ignoreEnumMembers: fIgnoreEnumMembers.value(), + }, + generated, + comments, + ) + + checkMap := mapChecker( + pass, + mapConfig{ + explicitExhaustiveMap: fExplicitExhaustiveMap, + checkGeneratedFiles: fCheckGenerated, + ignoreEnumMembers: fIgnoreEnumMembers.value(), + }, + generated, + comments, + ) + + filter := []ast.Node{ + &ast.SwitchStmt{}, + &ast.CompositeLit{}, + } + + inspect.WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool { + var proceed bool + switch n.(type) { + case *ast.SwitchStmt: + proceed, _ = checkSwitch(n, push, stack) + case *ast.CompositeLit: + proceed, _ = checkMap(n, push, stack) + } + return proceed }) + return nil, nil } diff --git a/exhaustive_test.go b/exhaustive_test.go index a725ed1..e68af27 100644 --- a/exhaustive_test.go +++ b/exhaustive_test.go @@ -112,7 +112,10 @@ func TestExhaustive(t *testing.T) { // Switch statements without enforce directive comment should not be checked during explicitly exhaustive // switch mode - run(t, "enforce-comment/...", func() { fExplicitExhaustiveSwitch = true }) + run(t, "enforce-comment/...", func() { + fExplicitExhaustiveSwitch = true + fExplicitExhaustiveMap = true + }) // To satisfy exhaustiveness, it is sufficient for each unique constant // value of the members to be listed, not each member by name. diff --git a/map.go b/map.go new file mode 100644 index 0000000..78ac6e5 --- /dev/null +++ b/map.go @@ -0,0 +1,147 @@ +package exhaustive + +import ( + "fmt" + "go/ast" + "go/types" + "golang.org/x/tools/go/analysis" + "regexp" + "strings" +) + +// mapConfig is configuration for mapChecker. +type mapConfig struct { + explicitExhaustiveMap bool + checkGeneratedFiles bool + ignoreEnumMembers *regexp.Regexp // can be nil +} + +// mapChecker returns a node visitor that checks exhaustiveness +// of enum keys in map literal for the supplied pass, and reports diagnostics if non-exhaustive. +// It expects to only see *ast.CompositeLit nodes. +func mapChecker(pass *analysis.Pass, cfg mapConfig, generated generatedCache, comments commentsCache) nodeVisitor { + return func(n ast.Node, push bool, stack []ast.Node) (bool, string) { + if !push { + // The proceed return value should not matter; it is ignored by + // inspector package for pop calls. + // Nevertheless, return true to be on the safe side for the future. + return true, resultNotPush + } + + file := stack[0].(*ast.File) + + if !cfg.checkGeneratedFiles && generated.IsGenerated(file) { + // Don't check this file. + // Return false because the children nodes of node `n` don't have to be checked. + return false, resultGeneratedFile + } + + lit := n.(*ast.CompositeLit) + + mapType, ok := pass.TypesInfo.Types[lit.Type].Type.(*types.Map) + if !ok { + namedType, ok2 := pass.TypesInfo.Types[lit.Type].Type.(*types.Named) + if !ok2 { + return true, resultNotMapLiteral + } + + mapType, ok = namedType.Underlying().(*types.Map) + if !ok { + return true, resultNotMapLiteral + } + } + + if len(lit.Elts) == 0 { + // because it may be used as an alternative for make(map[...]...) + return false, resultEmptyMapLiteral + } + + keyType, ok := mapType.Key().(*types.Named) + if !ok { + return true, resultMapKeyIsNotNamedType + } + + fileComments := comments.GetComments(file, pass.Fset) + var relatedComments []*ast.CommentGroup + for i := range stack { + // iterate over stack in the reverse order (from bottom to top) + node := stack[len(stack)-1-i] + switch node.(type) { + // need to check comments associated with following nodes, + // because logic of ast package doesn't allow to associate comment with *ast.CompositeLit + case *ast.CompositeLit, // stack[len(stack)-1] + *ast.ReturnStmt, // return ... + *ast.IndexExpr, // map[enum]...{...}[key] + *ast.CallExpr, // myfunc(map...) + *ast.UnaryExpr, // &map... + *ast.AssignStmt, // variable assignment (without var keyword) + *ast.DeclStmt, // var declaration, parent of *ast.GenDecl + *ast.GenDecl, // var declaration, parent of *ast.ValueSpec + *ast.ValueSpec: // var declaration + relatedComments = append(relatedComments, fileComments[node]...) + continue + } + // stop iteration on the first inappropriate node + break + } + + if !cfg.explicitExhaustiveMap && containsIgnoreDirective(relatedComments) { + // Skip checking of this map literal due to ignore directive comment. + // Still return true because there may be nested map literals + // that are not to be ignored. + return true, resultMapIgnoreComment + } + if cfg.explicitExhaustiveMap && !containsEnforceDirective(relatedComments) { + // Skip checking of this map literal due to missing enforce directive comment. + return true, resultMapNoEnforceComment + } + + keyPkg := keyType.Obj().Pkg() + if keyPkg == nil { + // The Go documentation says: nil for labels and objects in the Universe scope. + // This happens for the `error` type, for example. + return true, resultNilMapKeyTypePkg + } + + enumTyp := enumType{keyType.Obj()} + members, ok := importFact(pass, enumTyp) + if !ok { + return true, resultMapKeyNotEnum + } + + samePkg := keyPkg == pass.Pkg // do the map literal and the map key type (i.e. enum type) live in the same package? + checkUnexported := samePkg // we want to include unexported members in the exhaustiveness check only if we're in the same package + checklist := makeChecklist(members, keyPkg, checkUnexported, cfg.ignoreEnumMembers) + + for _, e := range lit.Elts { + expr, ok := e.(*ast.KeyValueExpr) + if !ok { + continue // is it possible for valid map literal? + } + analyzeCaseClauseExpr(expr.Key, pass.TypesInfo, checklist.found) + } + + if len(checklist.remaining()) == 0 { + // All enum members accounted for. + // Nothing to report. + return true, resultEnumMembersAccounted + } + + pass.Report(makeMapDiagnostic(lit, samePkg, enumTyp, members, checklist.remaining())) + return true, resultReportedDiagnostic + } +} + +// Makes a "missing map keys" diagnostic. +// samePkg should be true if the enum type and the map literal are defined in the same package. +func makeMapDiagnostic(lit *ast.CompositeLit, samePkg bool, enumTyp enumType, allMembers enumMembers, missingMembers map[string]struct{}) analysis.Diagnostic { + message := fmt.Sprintf("missing map keys of type %s: %s", + diagnosticEnumTypeName(enumTyp.TypeName, samePkg), + strings.Join(diagnosticMissingMembers(missingMembers, allMembers), ", ")) + + return analysis.Diagnostic{ + Pos: lit.Pos(), + End: lit.End(), + Message: message, + } +} diff --git a/switch.go b/switch.go index 5e81bc6..045c5a6 100644 --- a/switch.go +++ b/switch.go @@ -9,7 +9,6 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/ast/inspector" ) // nodeVisitor is like the visitor function used by Inspector.WithStack, @@ -25,6 +24,13 @@ const ( resultNotPush = "not push" resultGeneratedFile = "generated file" resultNoSwitchTag = "no switch tag" + resultEmptyMapLiteral = "empty map literal" + resultNotMapLiteral = "not map literal" + resultMapKeyIsNotNamedType = "map key is not named type" + resultNilMapKeyTypePkg = "nil map key type package" + resultMapKeyNotEnum = "map key not known enum type" + resultMapIgnoreComment = "map literal has ignore comment" + resultMapNoEnforceComment = "map literal has no enforce comment" resultTagNotValue = "switch tag not value type" resultTagNotNamed = "switch tag not named type" resultTagNoPkg = "switch tag does not belong to regular package" @@ -40,10 +46,7 @@ const ( // of enum switch statements for the supplied pass, and reports diagnostics for // switch statements that are non-exhaustive. // It expects to only see *ast.SwitchStmt nodes. -func switchStmtChecker(pass *analysis.Pass, cfg config) nodeVisitor { - generated := make(map[*ast.File]bool) // cached results - comments := make(map[*ast.File]ast.CommentMap) // cached results - +func switchStmtChecker(pass *analysis.Pass, cfg switchConfig, generated generatedCache, comments commentsCache) nodeVisitor { return func(n ast.Node, push bool, stack []ast.Node) (bool, string) { if !push { // The proceed return value should not matter; it is ignored by @@ -54,12 +57,7 @@ func switchStmtChecker(pass *analysis.Pass, cfg config) nodeVisitor { file := stack[0].(*ast.File) - // Determine if the file is a generated file, and save the result. - // If it is a generated file, don't check the file. - if _, ok := generated[file]; !ok { - generated[file] = isGeneratedFile(file) - } - if generated[file] && !cfg.checkGeneratedFiles { + if !cfg.checkGeneratedFiles && generated.IsGenerated(file) { // Don't check this file. // Return false because the children nodes of node `n` don't have to be checked. return false, resultGeneratedFile @@ -67,10 +65,7 @@ func switchStmtChecker(pass *analysis.Pass, cfg config) nodeVisitor { sw := n.(*ast.SwitchStmt) - if _, ok := comments[file]; !ok { - comments[file] = ast.NewCommentMap(pass.Fset, file, file.Comments) - } - switchComments := comments[file][sw] + switchComments := comments.GetComments(file, pass.Fset)[sw] if !cfg.explicitExhaustiveSwitch && containsIgnoreDirective(switchComments) { // Skip checking of this switch statement due to ignore directive comment. // Still return true because there may be nested switch statements @@ -114,9 +109,7 @@ func switchStmtChecker(pass *analysis.Pass, cfg config) nodeVisitor { checkUnexported := samePkg // we want to include unexported members in the exhaustiveness check only if we're in the same package checklist := makeChecklist(members, tagPkg, checkUnexported, cfg.ignoreEnumMembers) - hasDefaultCase := analyzeSwitchClauses(sw, pass.TypesInfo, func(val constantValue) { - checklist.found(val) - }) + hasDefaultCase := analyzeSwitchClauses(sw, pass.TypesInfo, checklist.found) if len(checklist.remaining()) == 0 { // All enum members accounted for. @@ -129,30 +122,19 @@ func switchStmtChecker(pass *analysis.Pass, cfg config) nodeVisitor { // So don't report. return true, resultDefaultCaseSuffices } - pass.Report(makeDiagnostic(sw, samePkg, enumTyp, members, checklist.remaining())) + pass.Report(makeSwitchDiagnostic(sw, samePkg, enumTyp, members, checklist.remaining())) return true, resultReportedDiagnostic } } -// config is configuration for checkSwitchStatements. -type config struct { +// switchConfig is configuration for switchStmtChecker. +type switchConfig struct { explicitExhaustiveSwitch bool defaultSignifiesExhaustive bool checkGeneratedFiles bool ignoreEnumMembers *regexp.Regexp // can be nil } -// checkSwitchStatements checks exhaustiveness of enum switch statements for the supplied -// pass. It reports switch statements that are not exhaustive via pass.Report. -func checkSwitchStatements(pass *analysis.Pass, inspect *inspector.Inspector, cfg config) { - f := switchStmtChecker(pass, cfg) - - inspect.WithStack([]ast.Node{&ast.SwitchStmt{}}, func(n ast.Node, push bool, stack []ast.Node) bool { - proceed, _ := f(n, push, stack) - return proceed - }) -} - func isDefaultCase(c *ast.CaseClause) bool { return c.List == nil // see doc comment on List field } @@ -300,7 +282,7 @@ func diagnosticEnumTypeName(enumType *types.TypeName, samePkg bool) string { // Makes a "missing cases in switch" diagnostic. // samePkg should be true if the enum type and the switch statement are defined // in the same package. -func makeDiagnostic(sw *ast.SwitchStmt, samePkg bool, enumTyp enumType, allMembers enumMembers, missingMembers map[string]struct{}) analysis.Diagnostic { +func makeSwitchDiagnostic(sw *ast.SwitchStmt, samePkg bool, enumTyp enumType, allMembers enumMembers, missingMembers map[string]struct{}) analysis.Diagnostic { message := fmt.Sprintf("missing cases in switch of type %s: %s", diagnosticEnumTypeName(enumTyp.TypeName, samePkg), strings.Join(diagnosticMissingMembers(missingMembers, allMembers), ", ")) diff --git a/switch_test.go b/switch_test.go index 622bb2f..7082a41 100644 --- a/switch_test.go +++ b/switch_test.go @@ -83,7 +83,7 @@ func TestDiagnosticMissingMembers(t *testing.T) { } // This test mainly exists to ensure stability of the diagnostic message format. -func TestMakeDiagnostic(t *testing.T) { +func TestMakeSwitchDiagnostic(t *testing.T) { sw := &ast.SwitchStmt{ Switch: 1, Body: &ast.BlockStmt{ @@ -110,7 +110,7 @@ func TestMakeDiagnostic(t *testing.T) { checkEnumMembersLiteral("Biome", allMembers) missingMembers := map[string]struct{}{"Savanna": {}, "Desert": {}} - got := makeDiagnostic(sw, samePkg, enumTyp, allMembers, missingMembers) + got := makeSwitchDiagnostic(sw, samePkg, enumTyp, allMembers, missingMembers) want := analysis.Diagnostic{ Pos: 1, End: 11, diff --git a/testdata/src/duplicate-enum-value/duplicate_enum_value.go b/testdata/src/duplicate-enum-value/duplicate_enum_value.go index 83793e0..e781f4a 100644 --- a/testdata/src/duplicate-enum-value/duplicate_enum_value.go +++ b/testdata/src/duplicate-enum-value/duplicate_enum_value.go @@ -36,4 +36,10 @@ func _s(c Chart) { case Sunburst: case Area: } + + _ = map[Chart]int{ // want "^missing map keys of type Chart: Pie\\|circle$" + Line: 1, + Sunburst: 2, + Area: 3, + } } diff --git a/testdata/src/duplicate-enum-value/otherpkg/otherpkg.go b/testdata/src/duplicate-enum-value/otherpkg/otherpkg.go index f60a7c8..1bcff93 100644 --- a/testdata/src/duplicate-enum-value/otherpkg/otherpkg.go +++ b/testdata/src/duplicate-enum-value/otherpkg/otherpkg.go @@ -16,6 +16,20 @@ func _p() { switch r { case d.DefaultRiver, d.Yamuna, d.Kaveri: } + + // should not report missing DefaultRiver, since it has same value as Ganga + _ = map[d.River]int{ + d.Ganga: 1, + d.Yamuna: 2, + d.Kaveri: 3, + } + + // should not report missing Ganga, since it has same value as DefaultRiver + _ = map[d.River]int{ + d.DefaultRiver: 1, + d.Yamuna: 2, + d.Kaveri: 3, + } } func _q() { @@ -25,6 +39,13 @@ func _q() { switch s { case d.TamilNadu, d.Kerala, d.Karnataka: } + + // should not report missing DefaultState, since it has same value as TamilNadu + _ = map[d.State]int{ + d.TamilNadu: 1, + d.Kerala: 2, + d.Karnataka: 3, + } } func _r() { @@ -40,6 +61,14 @@ func _r() { switch s { // want "^missing cases in switch of type duplicateenumvalue.State: TamilNadu\\|DefaultState, Kerala$" case d.Karnataka: } + + _ = map[d.River]int{ // want "^missing map keys of type duplicateenumvalue.River: DefaultRiver\\|Ganga, Kaveri$" + d.Yamuna: 1, + } + + _ = map[d.State]int{ // want "^missing map keys of type duplicateenumvalue.State: TamilNadu\\|DefaultState, Kerala$" + d.Karnataka: 1, + } } func _s(c d.Chart) { @@ -48,4 +77,10 @@ func _s(c d.Chart) { case d.Sunburst: case d.Area: } + + _ = map[d.Chart]int{ // want "^missing map keys of type duplicateenumvalue.Chart: Pie$" + d.Line: 1, + d.Sunburst: 2, + d.Area: 3, + } } diff --git a/testdata/src/enforce-comment/enforce_comment_map.go b/testdata/src/enforce-comment/enforce_comment_map.go new file mode 100644 index 0000000..719ac56 --- /dev/null +++ b/testdata/src/enforce-comment/enforce_comment_map.go @@ -0,0 +1,324 @@ +package enforcecomment + +import "fmt" + +var _ = map[Direction]int{ + N: 1, +} + +//exhaustive:enforce +var _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, +} + +//exhaustive:enforce +var ( + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[N] + _ = fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) +) + +var ( + //exhaustive:enforce + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = &map[Direction]int{ + N: 1, + } + _ = fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) +) + +func returnMap() map[Direction]int { + switch 0 { + case 1: + // some other comment + //exhaustive:enforce + // some other comment + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 2: + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 3: + return map[Direction]int{ + N: 1, + } + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ //exhaustive:enforce + N: 1, + } + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ + //exhaustive:enforce + N: 1, + } + } + return nil +} + +func returnValueFromMap(d Direction) int { + switch 0 { + case 1: + // some other comment + //exhaustive:enforce + // some other comment + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + case 2: + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + case 3: + return map[Direction]int{ + N: 1, + }[d] + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ //exhaustive:enforce + N: 1, + }[d] + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ + //exhaustive:enforce + N: 1, + }[d] + } + return 0 +} + +func returnFuncCallWithMap() error { + switch 0 { + case 1: + // some other comment + //exhaustive:enforce + // some other comment + return fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + + case 2: + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + return fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + + case 3: + return fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return fmt.Errorf("%v", map[Direction]int{ //exhaustive:enforce + N: 1, + }) + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return fmt.Errorf("%v", map[Direction]int{ + //exhaustive:enforce + N: 1, + }) + } + return nil +} + +func returnPointerToMap() *map[Direction]int { + switch 0 { + case 1: + // some other comment + //exhaustive:enforce + // some other comment + return &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 2: + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + return &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 3: + return &map[Direction]int{ + N: 1, + } + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return &map[Direction]int{ //exhaustive:enforce + N: 1, + } + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return &map[Direction]int{ + //exhaustive:enforce + N: 1, + } + } + return nil +} + +func assignMapLiteral() { + // some other comment + //exhaustive:enforce + // some other comment + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + //exhaustive:enforce + a := map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + //exhaustive:enforce + b, ok := map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }, 10 + + _, _, _ = a, b, ok + + _ = map[Direction]int{ + N: 1, + } + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ //exhaustive:enforce + N: 1, + } + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ + //exhaustive:enforce + N: 1, + } +} + +func assignValueFromMapLiteral(d Direction) { + // some other comment + //exhaustive:enforce + // some other comment + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + //exhaustive:enforce ... more arbitrary comment content (e.g. an explanation) ... + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + //exhaustive:enforce + a := map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[N] + + //exhaustive:enforce + b, ok := map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[N] + + _, _, _ = a, b, ok + + // this should report. + _ = map[Direction]int{ + N: 1, + }[d] + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ //exhaustive:enforce + N: 1, + }[d] + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ + //exhaustive:enforce + N: 1, + }[d] +} + +func localVarDeclaration() { + var _ = map[Direction]int{ + N: 1, + } + + //exhaustive:enforce + var _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + //exhaustive:enforce + var ( + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[N] + _ = fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + ) + + var ( + //exhaustive:enforce + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = &map[Direction]int{ + N: 1, + } + _ = fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) + ) +} diff --git a/testdata/src/enforce-comment/enforce_comment.go b/testdata/src/enforce-comment/enforce_comment_switch.go similarity index 100% rename from testdata/src/enforce-comment/enforce_comment.go rename to testdata/src/enforce-comment/enforce_comment_switch.go diff --git a/testdata/src/general/dotimport/dotimport.go b/testdata/src/general/dotimport/dotimport.go index 7c6e539..6dccfe3 100644 --- a/testdata/src/general/dotimport/dotimport.go +++ b/testdata/src/general/dotimport/dotimport.go @@ -11,6 +11,10 @@ func _dot() { switch p { // want "^missing cases in switch of type bar.Phylum: Chordata, Mollusca$" case Echinodermata: } + + _ = map[Phylum]int{ // want "^missing map keys of type bar.Phylum: Chordata, Mollusca$" + Echinodermata: 1, + } } func _mixed() { @@ -19,4 +23,9 @@ func _mixed() { case Echinodermata: case barpkg.Chordata: } + + _ = map[bar.Phylum]int{ // want "^missing map keys of type bar.Phylum: Mollusca$" + Echinodermata: 1, + barpkg.Chordata: 2, + } } diff --git a/testdata/src/general/x/general.go b/testdata/src/general/x/general.go index 6455311..19282d3 100644 --- a/testdata/src/general/x/general.go +++ b/testdata/src/general/x/general.go @@ -49,6 +49,12 @@ func _a() { case W: default: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, directionInvalid$" + N: 1, + S: 2, + W: 3, + } } func _b() { @@ -62,6 +68,11 @@ func _b() { case bar.Chordata: case bar.Echinodermata: } + + _ = map[bar.Phylum]int{ // want "^missing map keys of type bar.Phylum: Mollusca$" + bar.Chordata: 1, + bar.Echinodermata: 2, + } } func _j() { @@ -72,6 +83,11 @@ func _j() { case barpkg.Chordata: case barpkg.Echinodermata: } + + _ = map[barpkg.Phylum]int{ // want "^missing map keys of type bar.Phylum: Mollusca$" + barpkg.Chordata: 1, + barpkg.Echinodermata: 2, + } } func _f() { @@ -136,6 +152,12 @@ func _o() { case bar.Echinodermata: case h.Mollusca: } + + _ = map[bar.Phylum]int{ // want "^missing map keys of type bar.Phylum: Mollusca$" + bar.Chordata: 1, + bar.Echinodermata: 2, + h.Mollusca: 3, + } } var ErrFoo = errors.New("foo") @@ -150,6 +172,11 @@ func _p() { case nil: case ErrFoo: } + + _ = map[error]int{ + nil: 1, + ErrFoo: 2, + } } func _q() { @@ -173,6 +200,19 @@ func _q() { case fs.ModeSticky: case fs.ModeIrregular: } + + _ = map[fs.FileMode]int{ // want "^missing map keys of type fs.FileMode: ModeDevice, ModeSetuid, ModeSetgid, ModeType, ModePerm$" + os.ModeDir: 1, + os.ModeAppend: 2, + os.ModeExclusive: 3, + fs.ModeTemporary: 4, + fs.ModeSymlink: 5, + fs.ModeNamedPipe: 6, + os.ModeSocket: 7, + fs.ModeCharDevice: 8, + fs.ModeSticky: 9, + fs.ModeIrregular: 10, + } } func _r(d Direction) { @@ -186,10 +226,42 @@ func _r(d Direction) { case W: case 5: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: S, directionInvalid$" + N: 1, + E: 2, + 3: 3, + W: 4, + 5: 5, + } } func _s(u bar.Uppercase) { switch u { case bar.ReallyExported: } + + _ = map[bar.Uppercase]int{ + bar.ReallyExported: 1, + } +} + +func mapTypeAlias() { + type myMapAlias = map[Direction]int + + _ = myMapAlias{ // want "^missing map keys of type Direction: S, directionInvalid$" + N: 1, + E: 2, + W: 4, + } +} + +func customMapType() { + type myMap map[Direction]int + + _ = myMap{ // want "^missing map keys of type Direction: S, directionInvalid$" + N: 1, + E: 2, + W: 4, + } } diff --git a/testdata/src/general/x/irrelevant.go b/testdata/src/general/x/irrelevant.go index aad8a78..eb1d212 100644 --- a/testdata/src/general/x/irrelevant.go +++ b/testdata/src/general/x/irrelevant.go @@ -24,6 +24,10 @@ func _d() { switch a { case PlainIntA: } + + _ = map[int]int{ + PlainIntA: 1, + } } type NamedButNotEnum int @@ -36,4 +40,18 @@ func _e() { switch a { case 1: } + + _ = map[NamedButNotEnum]int{ + 1: 1, + } +} + +func emptyMapShouldBeIgnored() { + // because it can be used instead of make(...) + + _ = map[barpkg.Phylum]int{} + + _ = map[barpkg.Phylum]int{ // want "^missing map keys of type bar.Phylum: Echinodermata, Mollusca$" + barpkg.Chordata: 1, + } } diff --git a/testdata/src/general/x/is_exhaustive.go b/testdata/src/general/x/is_exhaustive.go index 3a19ca6..bed604e 100644 --- a/testdata/src/general/x/is_exhaustive.go +++ b/testdata/src/general/x/is_exhaustive.go @@ -9,6 +9,14 @@ func _l() { switch d { case N, E, S, W, directionInvalid: } + + _ = map[Direction]int{ + N: 1, + E: 2, + S: 3, + W: 4, + directionInvalid: 5, + } } func _m() { @@ -16,4 +24,10 @@ func _m() { switch p { case bar.Echinodermata, bar.Mollusca, bar.Chordata: } + + _ = map[bar.Phylum]int{ + bar.Echinodermata: 1, + bar.Mollusca: 2, + bar.Chordata: 3, + } } diff --git a/testdata/src/general/x/paren.go b/testdata/src/general/x/paren.go index e49a960..dc82d82 100644 --- a/testdata/src/general/x/paren.go +++ b/testdata/src/general/x/paren.go @@ -1,6 +1,6 @@ package x -func _k(d Direction) { +func _k(d Direction) { // Parenthesized values in case statements. switch d { // want "^missing cases in switch of type Direction: S, directionInvalid$" case (N): @@ -14,4 +14,10 @@ func _k(d Direction) { case E: case W: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: S, directionInvalid$" + (N): 1, + (E): 2, + (((W))): 3, + } } diff --git a/testdata/src/generated-file/check-generated-off/generated_0.go b/testdata/src/generated-file/check-generated-off/generated_0.go index d48adb3..b4de3c2 100644 --- a/testdata/src/generated-file/check-generated-off/generated_0.go +++ b/testdata/src/generated-file/check-generated-off/generated_0.go @@ -7,4 +7,8 @@ func _0() { switch d { case N: } + + _ = map[Direction]int{ + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-off/generated_3.go b/testdata/src/generated-file/check-generated-off/generated_3.go index 6988da5..3aaa8a1 100644 --- a/testdata/src/generated-file/check-generated-off/generated_3.go +++ b/testdata/src/generated-file/check-generated-off/generated_3.go @@ -9,4 +9,8 @@ func _3() { switch d { case N: } + + _ = map[Direction]int{ + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-off/generated_4.go b/testdata/src/generated-file/check-generated-off/generated_4.go index 5d1e8f7..f3c64c9 100644 --- a/testdata/src/generated-file/check-generated-off/generated_4.go +++ b/testdata/src/generated-file/check-generated-off/generated_4.go @@ -11,4 +11,8 @@ func _4() { switch d { case N: } + + _ = map[Direction]int{ + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-off/generated_doc_0.go b/testdata/src/generated-file/check-generated-off/generated_doc_0.go index ee62fdd..13f7b1f 100644 --- a/testdata/src/generated-file/check-generated-off/generated_doc_0.go +++ b/testdata/src/generated-file/check-generated-off/generated_doc_0.go @@ -10,4 +10,8 @@ func _doc_0() { switch d { case N: } + + _ = map[Direction]int{ + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-off/generated_doc_1.go b/testdata/src/generated-file/check-generated-off/generated_doc_1.go index f13df1c..03402ed 100644 --- a/testdata/src/generated-file/check-generated-off/generated_doc_1.go +++ b/testdata/src/generated-file/check-generated-off/generated_doc_1.go @@ -6,4 +6,8 @@ func _doc_1() { switch d { case N: } + + _ = map[Direction]int{ + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-off/not_generated_1.go b/testdata/src/generated-file/check-generated-off/not_generated_1.go index e6cb522..719d38d 100644 --- a/testdata/src/generated-file/check-generated-off/not_generated_1.go +++ b/testdata/src/generated-file/check-generated-off/not_generated_1.go @@ -7,6 +7,10 @@ func _1() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: /*-style comments don't count. diff --git a/testdata/src/generated-file/check-generated-off/not_generated_2.go b/testdata/src/generated-file/check-generated-off/not_generated_2.go index 4bbf8b3..04438e1 100644 --- a/testdata/src/generated-file/check-generated-off/not_generated_2.go +++ b/testdata/src/generated-file/check-generated-off/not_generated_2.go @@ -7,6 +7,10 @@ func _2() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: comment is too far into the file. diff --git a/testdata/src/generated-file/check-generated-off/not_generated_doc_2.go b/testdata/src/generated-file/check-generated-off/not_generated_doc_2.go index e8ffa3c..f9b7cfd 100644 --- a/testdata/src/generated-file/check-generated-off/not_generated_doc_2.go +++ b/testdata/src/generated-file/check-generated-off/not_generated_doc_2.go @@ -6,6 +6,10 @@ func _doc_2() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: /*-style comments don't count. diff --git a/testdata/src/generated-file/check-generated-on/generated_0.go b/testdata/src/generated-file/check-generated-on/generated_0.go index 10ea7ed..85a9c5c 100644 --- a/testdata/src/generated-file/check-generated-on/generated_0.go +++ b/testdata/src/generated-file/check-generated-on/generated_0.go @@ -7,4 +7,8 @@ func _0() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-on/generated_3.go b/testdata/src/generated-file/check-generated-on/generated_3.go index e01913e..51c7bb7 100644 --- a/testdata/src/generated-file/check-generated-on/generated_3.go +++ b/testdata/src/generated-file/check-generated-on/generated_3.go @@ -9,4 +9,8 @@ func _3() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-on/generated_4.go b/testdata/src/generated-file/check-generated-on/generated_4.go index badb879..85d69ef 100644 --- a/testdata/src/generated-file/check-generated-on/generated_4.go +++ b/testdata/src/generated-file/check-generated-on/generated_4.go @@ -11,4 +11,8 @@ func _4() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-on/generated_doc_0.go b/testdata/src/generated-file/check-generated-on/generated_doc_0.go index 901f4ff..1f3b071 100644 --- a/testdata/src/generated-file/check-generated-on/generated_doc_0.go +++ b/testdata/src/generated-file/check-generated-on/generated_doc_0.go @@ -10,4 +10,8 @@ func _doc_0() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-on/generated_doc_1.go b/testdata/src/generated-file/check-generated-on/generated_doc_1.go index 5449109..ea93f68 100644 --- a/testdata/src/generated-file/check-generated-on/generated_doc_1.go +++ b/testdata/src/generated-file/check-generated-on/generated_doc_1.go @@ -6,4 +6,8 @@ func _doc_1() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } diff --git a/testdata/src/generated-file/check-generated-on/not_generated_1.go b/testdata/src/generated-file/check-generated-on/not_generated_1.go index e6cb522..719d38d 100644 --- a/testdata/src/generated-file/check-generated-on/not_generated_1.go +++ b/testdata/src/generated-file/check-generated-on/not_generated_1.go @@ -7,6 +7,10 @@ func _1() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: /*-style comments don't count. diff --git a/testdata/src/generated-file/check-generated-on/not_generated_2.go b/testdata/src/generated-file/check-generated-on/not_generated_2.go index 4bbf8b3..04438e1 100644 --- a/testdata/src/generated-file/check-generated-on/not_generated_2.go +++ b/testdata/src/generated-file/check-generated-on/not_generated_2.go @@ -7,6 +7,10 @@ func _2() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: comment is too far into the file. diff --git a/testdata/src/generated-file/check-generated-on/not_generated_doc_2.go b/testdata/src/generated-file/check-generated-on/not_generated_doc_2.go index e8ffa3c..f9b7cfd 100644 --- a/testdata/src/generated-file/check-generated-on/not_generated_doc_2.go +++ b/testdata/src/generated-file/check-generated-on/not_generated_doc_2.go @@ -6,6 +6,10 @@ func _doc_2() { switch d { // want "^missing cases in switch of type Direction: E, S, W, directionInvalid$" case N: } + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } } // Explanation: /*-style comments don't count. diff --git a/testdata/src/ignore-comment/ignore_comment_map.go b/testdata/src/ignore-comment/ignore_comment_map.go new file mode 100644 index 0000000..93679de --- /dev/null +++ b/testdata/src/ignore-comment/ignore_comment_map.go @@ -0,0 +1,324 @@ +package ignorecomment + +import "fmt" + +var _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, +} + +//exhaustive:ignore +var _ = map[Direction]int{ + N: 1, +} + +//exhaustive:ignore +var ( + _ = map[Direction]int{ + N: 1, + } + _ = &map[Direction]int{ + N: 1, + } + _ = map[Direction]int{ + N: 1, + }[N] + _ = fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) +) + +var ( + //exhaustive:ignore + _ = map[Direction]int{ + N: 1, + } + _ = &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) +) + +func returnMap() map[Direction]int { + switch 0 { + case 1: + // some other comment + //exhaustive:ignore + // some other comment + return map[Direction]int{ + N: 1, + } + + case 2: + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + return map[Direction]int{ + N: 1, + } + + case 3: + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + } + } + return nil +} + +func returnValueFromMap(d Direction) int { + switch 0 { + case 1: + // some other comment + //exhaustive:ignore + // some other comment + return map[Direction]int{ + N: 1, + }[d] + + case 2: + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + return map[Direction]int{ + N: 1, + }[d] + + case 3: + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + }[d] + } + return 0 +} + +func returnFuncCallWithMap() error { + switch 0 { + case 1: + // some other comment + //exhaustive:ignore + // some other comment + return fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) + + case 2: + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + return fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) + + case 3: + return fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return fmt.Errorf("%v", map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + }) + } + return nil +} + +func returnPointerToMap() *map[Direction]int { + switch 0 { + case 1: + // some other comment + //exhaustive:ignore + // some other comment + return &map[Direction]int{ + N: 1, + } + + case 2: + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + return &map[Direction]int{ + N: 1, + } + + case 3: + return &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 4: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return &map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + case 5: + // this should report: according to go/ast, the comment is not considered to + // be associated with the return node. + return &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + } + } + return nil +} + +func assignMapLiteral() { + // some other comment + //exhaustive:ignore + // some other comment + _ = map[Direction]int{ + N: 1, + } + + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + _ = map[Direction]int{ + N: 1, + } + + //exhaustive:ignore + a := map[Direction]int{ + N: 1, + } + + //exhaustive:ignore + b, ok := map[Direction]int{ + N: 1, + }, 10 + + _, _, _ = a, b, ok + + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + } +} + +func assignValueFromMapLiteral(d Direction) { + // some other comment + //exhaustive:ignore + // some other comment + _ = map[Direction]int{ + N: 1, + }[d] + + //exhaustive:ignore ... more arbitrary comment content (e.g. an explanation) ... + _ = map[Direction]int{ + N: 1, + }[d] + + //exhaustive:ignore + a := map[Direction]int{ + N: 1, + }[N] + + //exhaustive:ignore + b, ok := map[Direction]int{ + N: 1, + }[N] + + _, _, _ = a, b, ok + + // this should report. + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ //exhaustive:ignore // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }[d] + + // this should report: according to go/ast, the comment is not considered to + // be associated with the assign node. + _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + //exhaustive:ignore + N: 1, + }[d] +} + +func localVarDeclaration() { + var _ = map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + + //exhaustive:ignore + var _ = map[Direction]int{ + N: 1, + } + + //exhaustive:ignore + var ( + _ = map[Direction]int{ + N: 1, + } + _ = &map[Direction]int{ + N: 1, + } + _ = map[Direction]int{ + N: 1, + }[N] + _ = fmt.Errorf("%v", map[Direction]int{ + N: 1, + }) + ) + + var ( + //exhaustive:ignore + _ = map[Direction]int{ + N: 1, + } + _ = &map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + } + _ = fmt.Errorf("%v", map[Direction]int{ // want "^missing map keys of type Direction: E, S, W, directionInvalid$" + N: 1, + }) + ) +} diff --git a/testdata/src/ignore-comment/ignore_comment.go b/testdata/src/ignore-comment/ignore_comment_switch.go similarity index 100% rename from testdata/src/ignore-comment/ignore_comment.go rename to testdata/src/ignore-comment/ignore_comment_switch.go diff --git a/testdata/src/ignore-enum-member/ignore_enum_member.go b/testdata/src/ignore-enum-member/ignore_enum_member.go index 53ba169..1ecd1d0 100644 --- a/testdata/src/ignore-enum-member/ignore_enum_member.go +++ b/testdata/src/ignore-enum-member/ignore_enum_member.go @@ -15,6 +15,10 @@ func _a() { switch e { // want "^missing cases in switch of type Exchange: Exchange_EXCHANGE_BINANCE$" case Exchange_EXCHANGE_BITMEX: } + + _ = map[Exchange]int{ // want "^missing map keys of type Exchange: Exchange_EXCHANGE_BINANCE$" + Exchange_EXCHANGE_BITMEX: 1, + } } func _b() { @@ -22,4 +26,8 @@ func _b() { switch p { // want "^missing cases in switch of type bar.Phylum: Mollusca$" case barpkg.Chordata: } + + _ = map[barpkg.Phylum]int{ // want "^missing map keys of type bar.Phylum: Mollusca$" + barpkg.Chordata: 1, + } } diff --git a/testdata/src/ignore-enum-member/same_value.go b/testdata/src/ignore-enum-member/same_value.go index e66473c..f0f312b 100644 --- a/testdata/src/ignore-enum-member/same_value.go +++ b/testdata/src/ignore-enum-member/same_value.go @@ -15,4 +15,8 @@ const ( func _c(a Access) { switch a { // want "^missing cases in switch of type Access: Standard, Group$" } + + _ = map[Access]int{ // want "^missing map keys of type Access: Standard, Group$" + 0: 0, + } } diff --git a/testdata/src/scope/allscope/allscope.go b/testdata/src/scope/allscope/allscope.go index 1cbdc7d..2a1f7a4 100644 --- a/testdata/src/scope/allscope/allscope.go +++ b/testdata/src/scope/allscope/allscope.go @@ -49,3 +49,39 @@ func _a() { case X: } } + +func _b() { + type T int // want T:"^C,D$" + + const ( + C T = iota + D + ) + + // must not report diagnostic here + _ = map[T]int{ + C: 1, + D: 2, + } + + _ = map[T]int{ // want "^missing map keys of type T: D$" + C: 1, + } + + type Q string // want Q:"^X,Y$" + + const ( + X Q = "x" + Y Q = "y" + ) + + // must not report diagnostic here + _ = map[Q]int{ + X: 1, + Y: 2, + } + + _ = map[Q]int{ // want "^missing map keys of type Q: Y$" + X: 1, + } +} diff --git a/testdata/src/scope/pkgscope/pkgscope.go b/testdata/src/scope/pkgscope/pkgscope.go index 9174176..4f46c82 100644 --- a/testdata/src/scope/pkgscope/pkgscope.go +++ b/testdata/src/scope/pkgscope/pkgscope.go @@ -47,3 +47,37 @@ func _a() { case X: } } + +func _b() { + type T int + + const ( + C T = iota + D + ) + + _ = map[T]int{ + C: 1, + D: 2, + } + + _ = map[T]int{ + C: 1, + } + + type Q string + + const ( + X Q = "x" + Y Q = "y" + ) + + _ = map[Q]int{ + X: 1, + Y: 2, + } + + _ = map[Q]int{ + X: 1, + } +} diff --git a/testdata/src/typealias/quux/quux.go b/testdata/src/typealias/quux/quux.go index ff87f6a..9e40b9e 100644 --- a/testdata/src/typealias/quux/quux.go +++ b/testdata/src/typealias/quux/quux.go @@ -1,28 +1,46 @@ package quux import ( - "typealias/bar" - "typealias/foo" + "typealias/bar" + "typealias/foo" ) func x() { - var v foo.T1 = foo.ReturnsT1() - switch v { // want "^missing cases in switch of type bar.T2: D, E$" - case foo.A: - case bar.B: - case foo.C: - case foo.D: - case foo.F: - case foo.H: - } + var v foo.T1 = foo.ReturnsT1() + switch v { // want "^missing cases in switch of type bar.T2: D, E$" + case foo.A: + case bar.B: + case foo.C: + case foo.D: + case foo.F: + case foo.H: + } - var w bar.T2 = foo.ReturnsT1() - switch w { // want "^missing cases in switch of type bar.T2: D, E$" - case foo.A: - case bar.B: - case foo.C: - case foo.D: - case foo.F: - case foo.H: - } + var w bar.T2 = foo.ReturnsT1() + switch w { // want "^missing cases in switch of type bar.T2: D, E$" + case foo.A: + case bar.B: + case foo.C: + case foo.D: + case foo.F: + case foo.H: + } + + _ = map[foo.T1]int{ // want "^missing map keys of type bar.T2: D, E$" + foo.A: 1, + bar.B: 2, + foo.C: 3, + foo.D: 4, + foo.F: 5, + foo.H: 6, + } + + _ = map[bar.T2]int{ // want "^missing map keys of type bar.T2: D, E$" + foo.A: 1, + bar.B: 2, + foo.C: 3, + foo.D: 4, + foo.F: 5, + foo.H: 6, + } } diff --git a/testdata/src/typealias/typealias.go b/testdata/src/typealias/typealias.go index f47e919..6cee873 100644 --- a/testdata/src/typealias/typealias.go +++ b/testdata/src/typealias/typealias.go @@ -32,15 +32,27 @@ func _a() { v := t1() switch v { } + + _ = map[typealias.T1]int{ + 0: 0, + } } func _b() { v := t2() switch v { } + + _ = map[typealias.T2]int{ + 0: 0, + } } func _c() { switch t9() { } + + _ = map[typealias.T9]int{ + 0: 0, + } } // -- @@ -71,18 +83,34 @@ const ( func _d() { switch t4() { } + + _ = map[typealias.T4]int{ + 0: 0, + } } func _e() { switch t10() { // want "^missing cases in switch of type typealias.T11: T11_A, T11_B$" } + + _ = map[typealias.T10]int{ // want "^missing map keys of type typealias.T11: T11_A, T11_B$" + 0: 0, + } } func _f() { switch t6() { } + + _ = map[typealias.T6]int{ + 0: 0, + } } func _g() { switch t15() { // want "^missing cases in switch of type typealias.T11: T11_A, T11_B$" } + + _ = map[typealias.T15]int{ // want "^missing map keys of type typealias.T11: T11_A, T11_B$" + 0: 0, + } } // -- @@ -112,18 +140,34 @@ const ( func _h() { switch t12() { } + + _ = map[typealias.T12]int{ + 0: 0, + } } func _i() { switch t13() { // want "^missing cases in switch of type otherpkg.T11: T11_A, T11_B, T11_C$" } + + _ = map[typealias.T13]int{ // want "^missing map keys of type otherpkg.T11: T11_A, T11_B, T11_C$" + 0: 0, + } } func _j() { switch t14() { } + + _ = map[typealias.T14]int{ + 0: 0, + } } func _k() { switch t17() { // want "^missing cases in switch of type otherpkg.T11: T11_A, T11_B, T11_C$" } + + _ = map[typealias.T17]int{ // want "^missing map keys of type otherpkg.T11: T11_A, T11_B, T11_C$" + 0: 0, + } } // -- @@ -140,6 +184,10 @@ func _l() { v := t18() switch v { // want "^missing cases in switch of type anotherpkg.T1: T1_A$" } + + _ = map[typealias.T18]int{ // want "^missing map keys of type anotherpkg.T1: T1_A$" + 0: 0, + } } // -- @@ -150,4 +198,8 @@ func _m() { v := d1() switch v { } + + _ = map[typealias.D1]int{ + struct{}{}: 0, + } }