Skip to content

Commit 8714673

Browse files
Merge pull request #776 from Microsoft/getOccurrencesThrow
Support getOccurrencesAtPosition for 'throw' keywords.
2 parents e49ff08 + fc46953 commit 8714673

9 files changed

+482
-1
lines changed

src/services/services.ts

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2635,6 +2635,11 @@ module ts {
26352635
return getReturnOccurrences(<ReturnStatement>node.parent);
26362636
}
26372637
break;
2638+
case SyntaxKind.ThrowKeyword:
2639+
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
2640+
return getThrowOccurrences(<ThrowStatement>node.parent);
2641+
}
2642+
break;
26382643
case SyntaxKind.TryKeyword:
26392644
case SyntaxKind.CatchKeyword:
26402645
case SyntaxKind.FinallyKeyword:
@@ -2752,12 +2757,108 @@ module ts {
27522757
}
27532758

27542759
var keywords: Node[] = []
2755-
forEachReturnStatement(<Block>(<FunctionDeclaration>func).body, returnStatement => {
2760+
forEachReturnStatement(<Block>func.body, returnStatement => {
27562761
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
27572762
});
27582763

2764+
// Include 'throw' statements that do not occur within a try block.
2765+
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
2766+
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
2767+
});
2768+
27592769
return map(keywords, getReferenceEntryFromNode);
27602770
}
2771+
2772+
function getThrowOccurrences(throwStatement: ThrowStatement) {
2773+
var owner = getThrowStatementOwner(throwStatement);
2774+
2775+
if (!owner) {
2776+
return undefined;
2777+
}
2778+
2779+
var keywords: Node[] = [];
2780+
2781+
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
2782+
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
2783+
});
2784+
2785+
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
2786+
// ability to "jump out" of the function, and include occurrences for both.
2787+
if (owner.kind === SyntaxKind.FunctionBlock) {
2788+
forEachReturnStatement(<Block>owner, returnStatement => {
2789+
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
2790+
});
2791+
}
2792+
2793+
return map(keywords, getReferenceEntryFromNode);
2794+
}
2795+
2796+
/**
2797+
* Aggregates all throw-statements within this node *without* crossing
2798+
* into function boundaries and try-blocks with catch-clauses.
2799+
*/
2800+
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
2801+
var statementAccumulator: ThrowStatement[] = []
2802+
aggregate(node);
2803+
return statementAccumulator;
2804+
2805+
function aggregate(node: Node): void {
2806+
if (node.kind === SyntaxKind.ThrowStatement) {
2807+
statementAccumulator.push(<ThrowStatement>node);
2808+
}
2809+
else if (node.kind === SyntaxKind.TryStatement) {
2810+
var tryStatement = <TryStatement>node;
2811+
2812+
if (tryStatement.catchBlock) {
2813+
aggregate(tryStatement.catchBlock);
2814+
}
2815+
else {
2816+
// Exceptions thrown within a try block lacking a catch clause
2817+
// are "owned" in the current context.
2818+
aggregate(tryStatement.tryBlock);
2819+
}
2820+
2821+
if (tryStatement.finallyBlock) {
2822+
aggregate(tryStatement.finallyBlock);
2823+
}
2824+
}
2825+
// Do not cross function boundaries.
2826+
else if (!isAnyFunction(node)) {
2827+
forEachChild(node, aggregate);
2828+
}
2829+
};
2830+
}
2831+
2832+
/**
2833+
* For lack of a better name, this function takes a throw statement and returns the
2834+
* nearest ancestor that is a try-block (whose try statement has a catch clause),
2835+
* function-block, or source file.
2836+
*/
2837+
function getThrowStatementOwner(throwStatement: ThrowStatement): Node {
2838+
var child: Node = throwStatement;
2839+
2840+
while (child.parent) {
2841+
var parent = child.parent;
2842+
2843+
if (parent.kind === SyntaxKind.FunctionBlock || parent.kind === SyntaxKind.SourceFile) {
2844+
return parent;
2845+
}
2846+
2847+
// A throw-statement is only owned by a try-statement if the try-statement has
2848+
// a catch clause, and if the throw-statement occurs within the try block.
2849+
if (parent.kind === SyntaxKind.TryStatement) {
2850+
var tryStatement = <TryStatement>parent;
2851+
2852+
if (tryStatement.tryBlock === child && tryStatement.catchBlock) {
2853+
return child;
2854+
}
2855+
}
2856+
2857+
child = parent;
2858+
}
2859+
2860+
return undefined;
2861+
}
27612862

27622863
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] {
27632864
var keywords: Node[] = [];
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)