Skip to content

Commit f49be29

Browse files
committed
no-else-after-return: use correct control flow analysis
1 parent 949760e commit f49be29

File tree

3 files changed

+102
-91
lines changed

3 files changed

+102
-91
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"dependencies": {
3737
"@fimbul/bifrost": "^0.6.0",
3838
"tslib": "^1.7.1",
39-
"tsutils": "^2.22.2"
39+
"tsutils": "^2.24.0"
4040
},
4141
"devDependencies": {
4242
"@fimbul/valtyr": "^0.6.0",
@@ -47,7 +47,7 @@
4747
"nyc": "^11.0.3",
4848
"rimraf": "^2.6.2",
4949
"tslint": "^5.8.0",
50-
"typescript": "~2.7.1"
50+
"typescript": "~2.8.1"
5151
},
5252
"peerDependencies": {
5353
"tslint": "^5.0.0",

rules/noElseAfterReturnRule.ts

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
import * as ts from 'typescript';
22
import * as Lint from 'tslint';
3-
import * as utils from 'tsutils';
43

54
import { isElseIf } from '../src/utils';
65
import { AbstractIfStatementWalker } from '../src/walker';
6+
import { getControlFlowEnd, isReturnStatement } from 'tsutils';
77

88
const FAIL_MESSAGE = `unnecessary else after return`;
99

10-
// TODO respect break label in switch
11-
12-
const enum StatementType {
13-
None,
14-
Break,
15-
Return,
16-
}
17-
1810
export class Rule extends Lint.Rules.AbstractRule {
1911
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
2012
return this.applyWithWalker(new IfWalker(sourceFile, this.ruleName, undefined));
@@ -23,62 +15,16 @@ export class Rule extends Lint.Rules.AbstractRule {
2315

2416
class IfWalker extends AbstractIfStatementWalker<void> {
2517
protected _checkIfStatement(node: ts.IfStatement) {
26-
if (node.elseStatement !== undefined &&
18+
if (
19+
node.elseStatement !== undefined &&
2720
!isElseIf(node) &&
28-
isLastStatementReturn(node.thenStatement))
21+
endsWithReturnStatement(node.thenStatement)
22+
)
2923
this.addFailureAtNode(node.getChildAt(5 /*else*/, this.sourceFile), FAIL_MESSAGE);
3024
}
3125
}
3226

33-
function isLastStatementReturn(statement: ts.Statement | ts.BlockLike | ts.DefaultClause): boolean {
34-
return endsControlFlow(statement) === StatementType.Return;
35-
}
36-
37-
function endsControlFlow(statement: ts.Statement | ts.BlockLike | ts.DefaultClause): StatementType {
38-
// recurse into nested blocks
39-
while (utils.isBlockLike(statement)) {
40-
if (statement.statements.length === 0)
41-
return StatementType.None;
42-
43-
statement = statement.statements[statement.statements.length - 1];
44-
}
45-
46-
return isDefinitelyReturned(statement);
47-
}
48-
49-
function isDefinitelyReturned(statement: ts.Statement): StatementType {
50-
if (statement.kind === ts.SyntaxKind.ReturnStatement)
51-
return StatementType.Return;
52-
if (statement.kind === ts.SyntaxKind.BreakStatement)
53-
return StatementType.Break;
54-
55-
if (utils.isIfStatement(statement)) {
56-
if (statement.elseStatement === undefined)
57-
return StatementType.None;
58-
const then = endsControlFlow(statement.thenStatement);
59-
if (!then)
60-
return then;
61-
return Math.min(
62-
then,
63-
endsControlFlow(statement.elseStatement),
64-
);
65-
}
66-
67-
if (utils.isSwitchStatement(statement)) {
68-
let hasDefault = false;
69-
let fallthrough = false;
70-
for (const clause of statement.caseBlock.clauses) {
71-
const retVal = endsControlFlow(clause);
72-
if (retVal === StatementType.None) {
73-
fallthrough = true;
74-
} else if (retVal === StatementType.Break) {
75-
return StatementType.None;
76-
} else {
77-
fallthrough = false;
78-
}
79-
hasDefault = hasDefault || clause.kind === ts.SyntaxKind.DefaultClause;
80-
}
81-
return !fallthrough && hasDefault ? StatementType.Return : StatementType.None;
82-
}
83-
return StatementType.None;
27+
function endsWithReturnStatement(node: ts.Statement): boolean {
28+
const end = getControlFlowEnd(node);
29+
return end.end && end.statements.every(isReturnStatement);
8430
}
Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
1-
if (true) {
1+
if (condition) {
22
return;
33
} else {}
44
~~~~ [fail]
55

6-
if (true) return;
6+
if (condition) {
7+
while (true)
8+
return;
9+
} else {}
10+
~~~~ [fail]
11+
12+
if (condition) {
13+
while (false)
14+
return;
15+
} else {}
16+
17+
if (condition) {
18+
while (condition)
19+
return;
20+
} else {}
21+
22+
if (condition) return;
723
else ;
824
~~~~ [fail]
925

10-
if (true) {
26+
if (condition) {
1127
{
1228
return;
1329
}
1430
} else {}
1531
~~~~ [fail]
1632

17-
if (true) {
18-
if (true) {
33+
if (condition) {
34+
if (confition) {
1935
return;
2036
} else {
2137
~~~~ [fail]
@@ -24,46 +40,46 @@ if (true) {
2440
} else {}
2541
~~~~ [fail]
2642

27-
if (true) {
28-
if (true) {
43+
if (condition) {
44+
if (condition) {
2945
return;
3046
}
3147
} else {}
3248

33-
if (true) return;
49+
if (condition) return;
3450

35-
if (true) foo();
51+
if (condition) foo();
3652
else bar();
3753

38-
if (true) {
54+
if (condition) {
3955
return;
4056
}
4157

42-
if (true) {
58+
if (condition) {
4359
foo();
4460
} else {
4561
bar();
4662
}
4763

48-
if (true) {
64+
if (condition) {
4965
{
5066
}
5167
} else {}
5268

53-
if (true) {
54-
if (true) {
69+
if (condition) {
70+
if (condition) {
5571
return;
5672
} else {
5773
~~~~ [fail]
5874
}
5975
} else {}
6076

61-
if (true) {
62-
if (true) {
77+
if (condition) {
78+
if (condition) {
6379
}
6480
} else {}
6581

66-
if (true) {
82+
if (condition) {
6783
switch (foo) {
6884
case 'a':
6985
return;
@@ -75,7 +91,7 @@ if (true) {
7591
} else {}
7692
~~~~ [fail]
7793

78-
if (true) {
94+
if (condition) {
7995
switch (foo) {
8096
default:
8197
return;
@@ -85,7 +101,7 @@ if (true) {
85101
} else {}
86102
~~~~ [fail]
87103

88-
if (true) {
104+
if (condition) {
89105
switch (foo) {
90106
case 'a':
91107
return;
@@ -94,7 +110,7 @@ if (true) {
94110
}
95111
} else {}
96112

97-
if (true) {
113+
if (condition) {
98114
switch (foo) {
99115
case 'a':
100116
return;
@@ -105,7 +121,7 @@ if (true) {
105121
} else {}
106122
~~~~ [fail]
107123

108-
if (true) {
124+
if (condition) {
109125
switch (foo) {
110126
case 'a':
111127
return;
@@ -118,7 +134,7 @@ if (true) {
118134
} else {}
119135
~~~~ [fail]
120136

121-
if (true) {
137+
if (condition) {
122138
switch (foo) {
123139
case 'a':
124140
return;
@@ -128,18 +144,67 @@ if (true) {
128144
}
129145
} else {}
130146

131-
if (true) {
132-
}else if (true) {
147+
if (condition) {
148+
}else if (condition) {
133149
return;
134150
} else {}
135151

136-
if (true) {
152+
if (condition) {
137153
}else{
138-
if (true) {
154+
if (condition) {
139155
return;
140156
} else {}
141157
~~~~ [fail]
142158
}
143159

160+
if (condition) {
161+
throw null;
162+
} else {}
163+
164+
if (condition) {
165+
if (condition)
166+
return;
167+
else
168+
~~~~ [fail]
169+
return;
170+
} else {}
171+
~~~~ [fail]
172+
173+
switch (Boolean()) {
174+
case true:
175+
if (condition) {
176+
if (condition)
177+
break;
178+
return;
179+
} else {}
180+
break;
181+
case false:
182+
if (condition) {
183+
if (condition)
184+
return;
185+
return;
186+
} else {}
187+
~~~~ [fail]
188+
}
189+
190+
outer: for (;;) {
191+
if (condition) {
192+
switch (Boolean()) {
193+
case true:
194+
break;
195+
}
196+
return;
197+
} else {}
198+
~~~~ [fail]
199+
200+
if (condition) {
201+
switch (Boolean()) {
202+
case true:
203+
break outer;
204+
}
205+
return;
206+
} else {}
207+
}
208+
144209

145-
[fail]: unnecessary else after return
210+
[fail]: unnecessary else after return

0 commit comments

Comments
 (0)