Skip to content

Commit b4af881

Browse files
committed
feat: pass first invalid test
1 parent 38d8c7b commit b4af881

17 files changed

+5052
-1
lines changed

internal/linter/linter.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
177177
var childVisitor ast.Visitor
178178
var patternVisitor func(node *ast.Node)
179179
patternVisitor = func(node *ast.Node) {
180+
runListeners(rule.WildcardTokenKind, node)
180181
runListeners(node.Kind, node)
181182
kind := rule.ListenerOnAllowPattern(node.Kind)
182183
runListeners(kind, node)
@@ -200,8 +201,10 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
200201

201202
runListeners(rule.ListenerOnExit(kind), node)
202203
runListeners(rule.ListenerOnExit(node.Kind), node)
204+
runListeners(rule.WildcardExitTokenKind, node)
203205
}
204206
childVisitor = func(node *ast.Node) bool {
207+
runListeners(rule.WildcardTokenKind, node)
205208
runListeners(node.Kind, node)
206209

207210
switch node.Kind {
@@ -222,10 +225,11 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
222225
}
223226

224227
runListeners(rule.ListenerOnExit(node.Kind), node)
228+
runListeners(rule.WildcardExitTokenKind, node)
225229

226230
return false
227231
}
228-
file.Node.ForEachChild(childVisitor)
232+
patternVisitor(&file.Node)
229233
clear(registeredListeners)
230234
}
231235

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package code_path_analysis
2+
3+
type BreakContext struct {
4+
upper *BreakContext
5+
breakable bool
6+
label string
7+
brokenForkContext *ForkContext
8+
}
9+
10+
func NewBreakContext(state *CodePathState, breakable bool, label string) *BreakContext {
11+
return &BreakContext{
12+
upper: state.breakContext,
13+
breakable: breakable,
14+
label: label,
15+
brokenForkContext: NewEmptyForkContext(state.forkContext, nil /*forkLeavingPath*/),
16+
}
17+
}
18+
19+
// Creates new context for BreakStatement.
20+
func (s *CodePathState) PushBreakContext(breakable bool, label string) *BreakContext {
21+
s.breakContext = NewBreakContext(s, breakable, label)
22+
return s.breakContext
23+
}
24+
25+
// Removes the top item of the break context stack.
26+
func (s *CodePathState) PopBreakContext() *BreakContext {
27+
context := s.breakContext
28+
forkContext := s.forkContext
29+
30+
s.breakContext = context.upper
31+
32+
// Process this context here for other than switches and loops.
33+
if !context.breakable {
34+
brokenForkContext := context.brokenForkContext
35+
36+
if !brokenForkContext.IsEmpty() {
37+
brokenForkContext.Add(forkContext.Head())
38+
forkContext.ReplaceHead(brokenForkContext.MakeNext(0, -1))
39+
}
40+
}
41+
42+
return context
43+
}
44+
45+
// Makes a path for a `break` statement.
46+
// It registers the head segment to a context of `break`.
47+
// It makes new unreachable segment, then it set the head with the segment.
48+
func (s *CodePathState) MakeBreak(label string) {
49+
forkContext := s.forkContext
50+
51+
if !forkContext.IsReachable() {
52+
return
53+
}
54+
55+
context := s.getBreakContext(label)
56+
57+
if context != nil {
58+
context.brokenForkContext.Add(forkContext.Head())
59+
}
60+
61+
forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
62+
}
63+
64+
func (s *CodePathState) getBreakContext(label string) *BreakContext {
65+
context := s.breakContext
66+
67+
for context != nil {
68+
if label == "" && context.breakable {
69+
return context
70+
} else if context.label == label {
71+
return context
72+
}
73+
74+
context = context.upper
75+
}
76+
77+
return nil
78+
}
79+
80+
// Makes a path for a `continue` statement.
81+
//
82+
// It makes a looping path.
83+
// It makes new unreachable segment, then it set the head with the segment.
84+
func (s *CodePathState) MakeContinue(label string) {
85+
forkContext := s.forkContext
86+
87+
if !forkContext.IsReachable() {
88+
return
89+
}
90+
91+
context := s.getContinueContext(label)
92+
93+
if context != nil {
94+
if context.continueDestSegments != nil {
95+
s.MakeLooped(forkContext.Head(), context.continueDestSegments)
96+
97+
// If the context is a for-in/of loop, this effects a break also.
98+
if context.kind == ForInStatement || context.kind == ForOfStatement {
99+
context.brokenForkContext.Add(forkContext.Head())
100+
}
101+
} else {
102+
context.continueForkContext.Add(forkContext.Head())
103+
}
104+
}
105+
forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
106+
}
107+
108+
// Gets a loop-context for a `continue` statement.
109+
func (s *CodePathState) getContinueContext(label string) *LoopContext {
110+
if label == "" {
111+
return s.loopContext
112+
}
113+
114+
context := s.loopContext
115+
for context != nil {
116+
if context.label == label {
117+
return context
118+
}
119+
context = context.upper
120+
}
121+
122+
return nil
123+
}
124+
125+
// Makes a path for a `return` statement.
126+
//
127+
// It registers the head segment to a context of `return`.
128+
// It makes new unreachable segment, then it set the head with the segment.
129+
func (s *CodePathState) MakeReturn() {
130+
forkContext := s.forkContext
131+
132+
if forkContext.IsReachable() {
133+
returnCtx := s.getReturnContext()
134+
if returnCtx != nil {
135+
returnCtx.returnedForkContext.Add(forkContext.Head())
136+
}
137+
forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
138+
}
139+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package code_path_analysis
2+
3+
type ChainContext struct {
4+
upper *ChainContext
5+
countChoiceContext int
6+
}
7+
8+
func NewChainContext(state *CodePathState) *ChainContext {
9+
return &ChainContext{
10+
upper: state.chainContext,
11+
countChoiceContext: 0,
12+
}
13+
}
14+
15+
// Push a new `ChainExpression` context to the stack.
16+
// This method is called on entering to each `ChainExpression` node.
17+
// This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
18+
func (s *CodePathState) PushChainContext() {
19+
s.chainContext = NewChainContext(s)
20+
}
21+
22+
// Pop a `ChainExpression` context from the stack.
23+
// This method is called on exiting from each `ChainExpression` node.
24+
// This merges all forks of the last optional chaining.
25+
func (s *CodePathState) PopChainContext() {
26+
context := s.chainContext
27+
s.chainContext = context.upper
28+
29+
// pop all choice contexts of this.
30+
for i := context.countChoiceContext; i > 0; i-- {
31+
s.PopChoiceContext()
32+
}
33+
}
34+
35+
// Create a choice context for optional access.
36+
// This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
37+
// This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
38+
func (s *CodePathState) MakeOptionalNode() {
39+
if s.chainContext != nil {
40+
s.chainContext.countChoiceContext += 1
41+
s.PushChoiceContext("??", false)
42+
}
43+
}
44+
45+
// Create a fork.
46+
// This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
47+
func (s *CodePathState) MakeOptionalRight() {
48+
if s.chainContext != nil {
49+
s.MakeLogicalRight()
50+
}
51+
}

0 commit comments

Comments
 (0)