-
Notifications
You must be signed in to change notification settings - Fork 11
/
exhaustive.go
152 lines (131 loc) · 5.48 KB
/
exhaustive.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package exhaustive
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
func init() {
registerFlags()
}
func registerFlags() {
Analyzer.Flags.Var(&fCheck, CheckFlag, "comma-separated list of program `elements` to check for exhaustiveness; supported element values: switch, map")
Analyzer.Flags.BoolVar(&fExplicitExhaustiveSwitch, ExplicitExhaustiveSwitchFlag, false, `check switch statement only if associated with "//exhaustive:enforce" comment`)
Analyzer.Flags.BoolVar(&fExplicitExhaustiveMap, ExplicitExhaustiveMapFlag, false, `check map literal only if associated with "//exhaustive:enforce" comment`)
Analyzer.Flags.BoolVar(&fCheckGenerated, CheckGeneratedFlag, false, "check generated files")
Analyzer.Flags.BoolVar(&fDefaultSignifiesExhaustive, DefaultSignifiesExhaustiveFlag, false, "switch statement is unconditionally exhaustive if it has a default case")
Analyzer.Flags.BoolVar(&fDefaultCaseRequired, DefaultCaseRequiredFlag, false, "switch statement requires default case even if exhaustive")
Analyzer.Flags.Var(&fIgnoreEnumMembers, IgnoreEnumMembersFlag, "ignore constants matching `regexp`")
Analyzer.Flags.Var(&fIgnoreEnumTypes, IgnoreEnumTypesFlag, "ignore types matching `regexp`")
Analyzer.Flags.BoolVar(&fPackageScopeOnly, PackageScopeOnlyFlag, false, "only discover enums declared in file-level blocks")
var unused string
Analyzer.Flags.StringVar(&unused, IgnorePatternFlag, "", "no effect (deprecated); use -"+IgnoreEnumMembersFlag)
Analyzer.Flags.StringVar(&unused, CheckingStrategyFlag, "", "no effect (deprecated)")
}
// Flag names used by the analyzer. These are exported for use by analyzer
// driver programs.
const (
CheckFlag = "check"
ExplicitExhaustiveSwitchFlag = "explicit-exhaustive-switch"
ExplicitExhaustiveMapFlag = "explicit-exhaustive-map"
CheckGeneratedFlag = "check-generated"
DefaultSignifiesExhaustiveFlag = "default-signifies-exhaustive"
DefaultCaseRequiredFlag = "default-case-required"
IgnoreEnumMembersFlag = "ignore-enum-members"
IgnoreEnumTypesFlag = "ignore-enum-types"
PackageScopeOnlyFlag = "package-scope-only"
// Deprecated flag names.
IgnorePatternFlag = "ignore-pattern" // Deprecated: use IgnoreEnumMembersFlag.
CheckingStrategyFlag = "checking-strategy" // Deprecated: no longer applicable.
)
// Flag values.
var (
fCheck = stringsFlag{elements: defaultCheckElements, filter: validCheckElement}
fExplicitExhaustiveSwitch bool
fExplicitExhaustiveMap bool
fCheckGenerated bool
fDefaultSignifiesExhaustive bool
fDefaultCaseRequired bool
fIgnoreEnumMembers regexpFlag
fIgnoreEnumTypes regexpFlag
fPackageScopeOnly bool
)
// resetFlags resets the flag variables to default values.
// Useful in tests.
func resetFlags() {
fCheck = stringsFlag{elements: defaultCheckElements, filter: validCheckElement}
fExplicitExhaustiveSwitch = false
fExplicitExhaustiveMap = false
fCheckGenerated = false
fDefaultSignifiesExhaustive = false
fDefaultCaseRequired = false
fIgnoreEnumMembers = regexpFlag{}
fIgnoreEnumTypes = regexpFlag{}
fPackageScopeOnly = false
}
// checkElement is a program element supported by the -check flag.
type checkElement string
const (
elementSwitch checkElement = "switch"
elementMap checkElement = "map"
)
func validCheckElement(s string) error {
switch checkElement(s) {
case elementSwitch:
return nil
case elementMap:
return nil
default:
return fmt.Errorf("invalid program element %q", s)
}
}
var defaultCheckElements = []string{
string(elementSwitch),
}
var Analyzer = &analysis.Analyzer{
Name: "exhaustive",
Doc: "check exhaustiveness of enum switch statements",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
FactTypes: []analysis.Fact{&enumMembersFact{}},
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for typ, members := range findEnums(pass, fPackageScopeOnly, pass.Pkg, inspect, pass.TypesInfo) {
exportFact(pass, typ, members)
}
generated := boolCache{compute: isGeneratedFile}
comments := commentCache{compute: fileCommentMap}
// NOTE: should not share the same inspect.WithStack call for different
// program elements: the visitor function for a program element may
// exit traversal early, but this shouldn't affect traversal for
// other program elements.
for _, e := range fCheck.elements {
switch checkElement(e) {
case elementSwitch:
conf := switchConfig{
explicit: fExplicitExhaustiveSwitch,
defaultSignifiesExhaustive: fDefaultSignifiesExhaustive,
defaultCaseRequired: fDefaultCaseRequired,
checkGenerated: fCheckGenerated,
ignoreConstant: fIgnoreEnumMembers.re,
ignoreType: fIgnoreEnumTypes.re,
}
checker := switchChecker(pass, conf, generated, comments)
inspect.WithStack([]ast.Node{&ast.SwitchStmt{}}, toVisitor(checker))
case elementMap:
conf := mapConfig{
explicit: fExplicitExhaustiveMap,
checkGenerated: fCheckGenerated,
ignoreConstant: fIgnoreEnumMembers.re,
ignoreType: fIgnoreEnumTypes.re,
}
checker := mapChecker(pass, conf, generated, comments)
inspect.WithStack([]ast.Node{&ast.CompositeLit{}}, toVisitor(checker))
default:
panic(fmt.Sprintf("unknown checkElement %v", e))
}
}
return nil, nil
}