Skip to content

Commit 16d969c

Browse files
Support getOccurrencesAtPosition for 'throw' keywords.
Also revised behavior for 'return' keywords in that when the position resides on a 'return' statement, 'throw' keywords in the same function scope that are not within a try-block are also highlighted.
1 parent debc653 commit 16d969c

File tree

7 files changed

+409
-1
lines changed

7 files changed

+409
-1
lines changed

src/services/services.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,11 @@ module ts {
23782378
return getReturnOccurrences(<ReturnStatement>node.parent);
23792379
}
23802380
break;
2381+
case SyntaxKind.ThrowKeyword:
2382+
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
2383+
return getThrowOccurrences(<ThrowStatement>node.parent);
2384+
}
2385+
break;
23812386
case SyntaxKind.TryKeyword:
23822387
case SyntaxKind.CatchKeyword:
23832388
case SyntaxKind.FinallyKeyword:
@@ -2491,13 +2496,98 @@ module ts {
24912496
}
24922497

24932498
var keywords: Node[] = []
2494-
forEachReturnStatement(<Block>(<FunctionDeclaration>func).body, returnStatement => {
2499+
forEachReturnStatement(<Block>func.body, returnStatement => {
24952500
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
24962501
});
24972502

2503+
// Include 'throw' statements that do not occur within a try block.
2504+
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
2505+
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
2506+
});
2507+
2508+
return map(keywords, getReferenceEntryFromNode);
2509+
}
2510+
2511+
function getThrowOccurrences(throwStatement: ThrowStatement) {
2512+
var owner = getContextualThrowStatementOwner(throwStatement);
2513+
2514+
if (!owner) {
2515+
return undefined;
2516+
}
2517+
2518+
var keywords: Node[] = [];
2519+
2520+
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
2521+
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
2522+
});
2523+
2524+
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
2525+
// ability to "jump out" of the function, and include occurrences for both.
2526+
if (owner.kind === SyntaxKind.FunctionBlock) {
2527+
forEachReturnStatement(<Block>owner, returnStatement => {
2528+
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
2529+
});
2530+
}
2531+
24982532
return map(keywords, getReferenceEntryFromNode);
24992533
}
25002534

2535+
/**
2536+
* Aggregates all throw-statements within this node *without* crossing
2537+
* into function boundaries and try-blocks.
2538+
*/
2539+
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
2540+
var statementAccumulator: ThrowStatement[] = []
2541+
aggregate(node);
2542+
return statementAccumulator;
2543+
2544+
function aggregate(node: Node): void {
2545+
if (node.kind === SyntaxKind.ThrowStatement) {
2546+
statementAccumulator.push(<ThrowStatement>node);
2547+
}
2548+
else if (node.kind === SyntaxKind.TryStatement) {
2549+
var tryStatement = <TryStatement>node;
2550+
2551+
if (tryStatement.catchBlock) {
2552+
aggregate(tryStatement.catchBlock);
2553+
}
2554+
if (tryStatement.finallyBlock) {
2555+
aggregate(tryStatement.finallyBlock);
2556+
}
2557+
}
2558+
// Do not cross function boundaries.
2559+
else if (!isAnyFunction(node)) {
2560+
forEachChild(node, aggregate);
2561+
}
2562+
};
2563+
}
2564+
2565+
/**
2566+
* For lack of a better name, this function takes a throw statement and returns the first
2567+
* encountered ancestor that is a try-block, function-block, or source file.
2568+
*/
2569+
function getContextualThrowStatementOwner(throwStatement: ThrowStatement): Node {
2570+
var child: Node = throwStatement;
2571+
2572+
while (child.parent) {
2573+
var parent = child.parent;
2574+
2575+
if (parent.kind === SyntaxKind.FunctionBlock || parent.kind === SyntaxKind.SourceFile) {
2576+
return parent;
2577+
}
2578+
2579+
// A throw-statement is only owned by a try-statement if it occurs in the try block.
2580+
// Otherwise, it is owned by the next closest function-block or try-block.
2581+
if (parent.kind === SyntaxKind.TryStatement && child === (<TryStatement>parent).tryBlock) {
2582+
return child;
2583+
}
2584+
2585+
child = parent;
2586+
}
2587+
2588+
return undefined;
2589+
}
2590+
25012591
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] {
25022592
var keywords: Node[] = [];
25032593

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(a: number) {
4+
//// try {
5+
//// throw "Hello";
6+
////
7+
//// try {
8+
//// throw 10;
9+
//// }
10+
//// catch (x) {
11+
//// [|return|] 100;
12+
//// }
13+
//// finally {
14+
//// throw 10;
15+
//// }
16+
//// }
17+
//// catch (x) {
18+
//// [|throw|] "Something";
19+
//// }
20+
//// finally {
21+
//// [|throw|] "Also something";
22+
//// }
23+
//// if (a > 0) {
24+
//// [|return|] (function () {
25+
//// return;
26+
//// return;
27+
//// return;
28+
////
29+
//// if (false) {
30+
//// return true;
31+
//// }
32+
//// throw "Hello!";
33+
//// })() || true;
34+
//// }
35+
////
36+
//// [|th/**/row|] 10;
37+
////
38+
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
39+
////
40+
//// [|return|];
41+
//// [|return|] true;
42+
//// [|throw|] false;
43+
////}
44+
45+
test.ranges().forEach(r => {
46+
goTo.position(r.start);
47+
48+
test.ranges().forEach(range => {
49+
verify.occurrencesAtPositionContains(range, false);
50+
});
51+
52+
verify.occurrencesAtPositionCount(test.ranges().length);
53+
});
54+
55+
goTo.marker();
56+
test.ranges().forEach(range => {
57+
verify.occurrencesAtPositionContains(range, false);
58+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(a: number) {
4+
//// try {
5+
//// throw "Hello";
6+
////
7+
//// try {
8+
//// [|t/**/hrow|] 10;
9+
//// }
10+
//// catch (x) {
11+
//// return 100;
12+
//// }
13+
//// finally {
14+
//// throw 10;
15+
//// }
16+
//// }
17+
//// catch (x) {
18+
//// throw "Something";
19+
//// }
20+
//// finally {
21+
//// throw "Also something";
22+
//// }
23+
//// if (a > 0) {
24+
//// return (function () {
25+
//// return;
26+
//// return;
27+
//// return;
28+
////
29+
//// if (false) {
30+
//// return true;
31+
//// }
32+
//// throw "Hello!";
33+
//// })() || true;
34+
//// }
35+
////
36+
//// throw 10;
37+
////
38+
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
39+
////
40+
//// return;
41+
//// return true;
42+
//// throw false;
43+
////}
44+
45+
test.ranges().forEach(r => {
46+
goTo.position(r.start);
47+
48+
test.ranges().forEach(range => {
49+
verify.occurrencesAtPositionContains(range, false);
50+
});
51+
52+
verify.occurrencesAtPositionCount(test.ranges().length);
53+
});
54+
55+
goTo.marker();
56+
test.ranges().forEach(range => {
57+
verify.occurrencesAtPositionContains(range, false);
58+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(a: number) {
4+
//// try {
5+
//// [|throw|] "Hello";
6+
////
7+
//// try {
8+
//// throw 10;
9+
//// }
10+
//// catch (x) {
11+
//// return 100;
12+
//// }
13+
//// finally {
14+
//// [|thr/**/ow|] 10;
15+
//// }
16+
//// }
17+
//// catch (x) {
18+
//// throw "Something";
19+
//// }
20+
//// finally {
21+
//// throw "Also something";
22+
//// }
23+
//// if (a > 0) {
24+
//// return (function () {
25+
//// return;
26+
//// return;
27+
//// return;
28+
////
29+
//// if (false) {
30+
//// return true;
31+
//// }
32+
//// throw "Hello!";
33+
//// })() || true;
34+
//// }
35+
////
36+
//// throw 10;
37+
////
38+
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
39+
////
40+
//// return;
41+
//// return true;
42+
//// throw false;
43+
////}
44+
45+
test.ranges().forEach(r => {
46+
goTo.position(r.start);
47+
48+
test.ranges().forEach(range => {
49+
verify.occurrencesAtPositionContains(range, false);
50+
});
51+
52+
verify.occurrencesAtPositionCount(test.ranges().length);
53+
});
54+
55+
goTo.marker();
56+
test.ranges().forEach(range => {
57+
verify.occurrencesAtPositionContains(range, false);
58+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(a: number) {
4+
//// try {
5+
//// throw "Hello";
6+
////
7+
//// try {
8+
//// throw 10;
9+
//// }
10+
//// catch (x) {
11+
//// return 100;
12+
//// }
13+
//// finally {
14+
//// throw 10;
15+
//// }
16+
//// }
17+
//// catch (x) {
18+
//// throw "Something";
19+
//// }
20+
//// finally {
21+
//// throw "Also something";
22+
//// }
23+
//// if (a > 0) {
24+
//// return (function () {
25+
//// [|return|];
26+
//// [|return|];
27+
//// [|return|];
28+
////
29+
//// if (false) {
30+
//// [|return|] true;
31+
//// }
32+
//// [|th/**/row|] "Hello!";
33+
//// })() || true;
34+
//// }
35+
////
36+
//// throw 10;
37+
////
38+
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
39+
////
40+
//// return;
41+
//// return true;
42+
//// throw false;
43+
////}
44+
45+
test.ranges().forEach(r => {
46+
goTo.position(r.start);
47+
48+
test.ranges().forEach(range => {
49+
verify.occurrencesAtPositionContains(range, false);
50+
});
51+
52+
verify.occurrencesAtPositionCount(test.ranges().length);
53+
});
54+
55+
goTo.marker();
56+
test.ranges().forEach(range => {
57+
verify.occurrencesAtPositionContains(range, false);
58+
});

0 commit comments

Comments
 (0)