Skip to content

Commit 69803d4

Browse files
Implemented getOccurrences for for/for-in/while/do-while loops and their breaks/continues.
This includes labelled break/continue.
1 parent e1f8aa7 commit 69803d4

File tree

1 file changed

+98
-14
lines changed

1 file changed

+98
-14
lines changed

src/services/services.ts

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,16 @@ module ts {
12961296
(<LabelledStatement>node.parent).label === node;
12971297
}
12981298

1299+
function isLabelledBy(node: Node, labelName: string) {
1300+
for (var owner = node.parent; owner && owner.kind === SyntaxKind.LabelledStatement; owner = owner.parent) {
1301+
if ((<LabelledStatement>owner).label.text === labelName) {
1302+
return true;
1303+
}
1304+
}
1305+
1306+
return false;
1307+
}
1308+
12991309
function isLabelName(node: Node): boolean {
13001310
return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node);
13011311
}
@@ -2181,8 +2191,20 @@ module ts {
21812191
}
21822192
break;
21832193
case SyntaxKind.BreakKeyword:
2184-
if (hasKind(node.parent, SyntaxKind.BreakStatement)) {
2185-
return getBreakStatementOccurences(<BreakOrContinueStatement>node.parent);
2194+
case SyntaxKind.ContinueKeyword:
2195+
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
2196+
return getBreakOrContinueStatementOccurences(<BreakOrContinueStatement>node.parent);
2197+
}
2198+
break;
2199+
case SyntaxKind.ForKeyword:
2200+
if (hasKind(node.parent, SyntaxKind.ForStatement) || hasKind(node.parent, SyntaxKind.ForInStatement)) {
2201+
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
2202+
}
2203+
break;
2204+
case SyntaxKind.WhileKeyword:
2205+
case SyntaxKind.DoKeyword:
2206+
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
2207+
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
21862208
}
21872209
break;
21882210
}
@@ -2281,6 +2303,66 @@ module ts {
22812303
return map(keywords, getReferenceEntryFromNode);
22822304
}
22832305

2306+
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): ReferenceEntry[] {
2307+
var keywords: Node[] = [];
2308+
2309+
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
2310+
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
2311+
if (loopNode.kind === SyntaxKind.DoStatement) {
2312+
var loopTokens = loopNode.getChildren();
2313+
2314+
for (var i = loopTokens.length - 1; i >= 0; i--) {
2315+
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
2316+
break;
2317+
}
2318+
}
2319+
}
2320+
}
2321+
2322+
// This switch tracks whether or not we're traversing into a construct that takes
2323+
// ownership over unlabelled 'break'/'continue' statements.
2324+
var onlyCheckLabelled = false;
2325+
2326+
forEachChild(loopNode.statement, function aggregateBreakContinues(node: Node) {
2327+
// This tracks the status of the flag before diving into the next node.
2328+
var lastOnlyCheckLabelled = onlyCheckLabelled;
2329+
2330+
switch (node.kind) {
2331+
case SyntaxKind.BreakStatement:
2332+
case SyntaxKind.ContinueStatement:
2333+
// If the 'break'/'continue' statement has a label, it must be one of our tracked labels.
2334+
if ((<BreakOrContinueStatement>node).label) {
2335+
var labelName = (<BreakOrContinueStatement>node).label.text;
2336+
if (isLabelledBy(loopNode, labelName)) {
2337+
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
2338+
}
2339+
}
2340+
// If not, we are free to add it if we haven't lost ownership of unlabeled break/continue statements.
2341+
else if (!onlyCheckLabelled) {
2342+
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
2343+
}
2344+
break;
2345+
2346+
case SyntaxKind.ForStatement:
2347+
case SyntaxKind.ForInStatement:
2348+
case SyntaxKind.DoStatement:
2349+
case SyntaxKind.WhileStatement:
2350+
case SyntaxKind.SwitchStatement:
2351+
onlyCheckLabelled = true;
2352+
// Fall through
2353+
default:
2354+
// Do not cross function boundaries.
2355+
if (!isAnyFunction(node)) {
2356+
forEachChild(node, aggregateBreakContinues);
2357+
}
2358+
}
2359+
// Restore the last state.
2360+
onlyCheckLabelled = lastOnlyCheckLabelled;
2361+
});
2362+
2363+
return map(keywords, keywordToReferenceEntry);
2364+
}
2365+
22842366
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) {
22852367
var keywords: Node[] = [];
22862368

@@ -2317,28 +2399,30 @@ module ts {
23172399
return map(keywords, getReferenceEntryFromNode);
23182400
}
23192401

2320-
function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{
2321-
// TODO (drosen): Deal with labeled statements.
2322-
if (breakStatement.label) {
2323-
return undefined;
2324-
}
2325-
2402+
function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[]{
23262403
for (var owner = node.parent; owner; owner = owner.parent) {
23272404
switch (owner.kind) {
23282405
case SyntaxKind.ForStatement:
23292406
case SyntaxKind.ForInStatement:
23302407
case SyntaxKind.DoStatement:
23312408
case SyntaxKind.WhileStatement:
2332-
// TODO (drosen): Handle loops!
2333-
return undefined;
2334-
2409+
// The iteration statement is the owner if the break/continue statement is either unlabeled,
2410+
// or if the break/continue statement's label corresponds to one of the loop's labels.
2411+
if (!breakOrContinueStatement.label || isLabelledBy(owner, breakOrContinueStatement.label.text)) {
2412+
return getLoopBreakContinueOccurrences(<IterationStatement>owner)
2413+
}
2414+
break;
23352415
case SyntaxKind.SwitchStatement:
2336-
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
2337-
2416+
// A switch statement can only be the owner of an unlabeled break statement.
2417+
if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && !breakOrContinueStatement.label) {
2418+
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
2419+
}
2420+
break;
23382421
default:
23392422
if (isAnyFunction(owner)) {
23402423
return undefined;
23412424
}
2425+
break;
23422426
}
23432427
}
23442428

@@ -2347,7 +2431,7 @@ module ts {
23472431

23482432
// returns true if 'node' is defined and has a matching 'kind'.
23492433
function hasKind(node: Node, kind: SyntaxKind) {
2350-
return !!(node && node.kind === kind);
2434+
return node !== undefined && node.kind === kind;
23512435
}
23522436

23532437
// Null-propagating 'parent' function.

0 commit comments

Comments
 (0)