Merge pull request #776 from Microsoft/getOccurrencesThrow

Support getOccurrencesAtPosition for 'throw' keywords.
This commit is contained in:
Daniel Rosenwasser
2014-09-30 14:06:14 -07:00
9 changed files with 482 additions and 1 deletions

View File

@@ -2635,6 +2635,11 @@ module ts {
return getReturnOccurrences(<ReturnStatement>node.parent);
}
break;
case SyntaxKind.ThrowKeyword:
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
return getThrowOccurrences(<ThrowStatement>node.parent);
}
break;
case SyntaxKind.TryKeyword:
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
@@ -2752,12 +2757,108 @@ module ts {
}
var keywords: Node[] = []
forEachReturnStatement(<Block>(<FunctionDeclaration>func).body, returnStatement => {
forEachReturnStatement(<Block>func.body, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
// Include 'throw' statements that do not occur within a try block.
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
return map(keywords, getReferenceEntryFromNode);
}
function getThrowOccurrences(throwStatement: ThrowStatement) {
var owner = getThrowStatementOwner(throwStatement);
if (!owner) {
return undefined;
}
var keywords: Node[] = [];
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
// ability to "jump out" of the function, and include occurrences for both.
if (owner.kind === SyntaxKind.FunctionBlock) {
forEachReturnStatement(<Block>owner, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
}
return map(keywords, getReferenceEntryFromNode);
}
/**
* Aggregates all throw-statements within this node *without* crossing
* into function boundaries and try-blocks with catch-clauses.
*/
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
var statementAccumulator: ThrowStatement[] = []
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.ThrowStatement) {
statementAccumulator.push(<ThrowStatement>node);
}
else if (node.kind === SyntaxKind.TryStatement) {
var tryStatement = <TryStatement>node;
if (tryStatement.catchBlock) {
aggregate(tryStatement.catchBlock);
}
else {
// Exceptions thrown within a try block lacking a catch clause
// are "owned" in the current context.
aggregate(tryStatement.tryBlock);
}
if (tryStatement.finallyBlock) {
aggregate(tryStatement.finallyBlock);
}
}
// Do not cross function boundaries.
else if (!isAnyFunction(node)) {
forEachChild(node, aggregate);
}
};
}
/**
* For lack of a better name, this function takes a throw statement and returns the
* nearest ancestor that is a try-block (whose try statement has a catch clause),
* function-block, or source file.
*/
function getThrowStatementOwner(throwStatement: ThrowStatement): Node {
var child: Node = throwStatement;
while (child.parent) {
var parent = child.parent;
if (parent.kind === SyntaxKind.FunctionBlock || parent.kind === SyntaxKind.SourceFile) {
return parent;
}
// A throw-statement is only owned by a try-statement if the try-statement has
// a catch clause, and if the throw-statement occurs within the try block.
if (parent.kind === SyntaxKind.TryStatement) {
var tryStatement = <TryStatement>parent;
if (tryStatement.tryBlock === child && tryStatement.catchBlock) {
return child;
}
}
child = parent;
}
return undefined;
}
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] {
var keywords: Node[] = [];

View File

@@ -0,0 +1,58 @@
/// <reference path='fourslash.ts' />
////function f(a: number) {
//// try {
//// throw "Hello";
////
//// try {
//// throw 10;
//// }
//// catch (x) {
//// [|return|] 100;
//// }
//// finally {
//// throw 10;
//// }
//// }
//// catch (x) {
//// [|throw|] "Something";
//// }
//// finally {
//// [|throw|] "Also something";
//// }
//// if (a > 0) {
//// [|return|] (function () {
//// return;
//// return;
//// return;
////
//// if (false) {
//// return true;
//// }
//// throw "Hello!";
//// })() || true;
//// }
////
//// [|th/**/row|] 10;
////
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
////
//// [|return|];
//// [|return|] true;
//// [|throw|] false;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});
goTo.marker();
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});

View File

@@ -0,0 +1,58 @@
/// <reference path='fourslash.ts' />
////function f(a: number) {
//// try {
//// throw "Hello";
////
//// try {
//// [|t/**/hrow|] 10;
//// }
//// catch (x) {
//// return 100;
//// }
//// finally {
//// throw 10;
//// }
//// }
//// catch (x) {
//// throw "Something";
//// }
//// finally {
//// throw "Also something";
//// }
//// if (a > 0) {
//// return (function () {
//// return;
//// return;
//// return;
////
//// if (false) {
//// return true;
//// }
//// throw "Hello!";
//// })() || true;
//// }
////
//// throw 10;
////
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
////
//// return;
//// return true;
//// throw false;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});
goTo.marker();
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});

View File

@@ -0,0 +1,58 @@
/// <reference path='fourslash.ts' />
////function f(a: number) {
//// try {
//// [|throw|] "Hello";
////
//// try {
//// throw 10;
//// }
//// catch (x) {
//// return 100;
//// }
//// finally {
//// [|thr/**/ow|] 10;
//// }
//// }
//// catch (x) {
//// throw "Something";
//// }
//// finally {
//// throw "Also something";
//// }
//// if (a > 0) {
//// return (function () {
//// return;
//// return;
//// return;
////
//// if (false) {
//// return true;
//// }
//// throw "Hello!";
//// })() || true;
//// }
////
//// throw 10;
////
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
////
//// return;
//// return true;
//// throw false;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});
goTo.marker();
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});

View File

@@ -0,0 +1,58 @@
/// <reference path='fourslash.ts' />
////function f(a: number) {
//// try {
//// throw "Hello";
////
//// try {
//// throw 10;
//// }
//// catch (x) {
//// return 100;
//// }
//// finally {
//// throw 10;
//// }
//// }
//// catch (x) {
//// throw "Something";
//// }
//// finally {
//// throw "Also something";
//// }
//// if (a > 0) {
//// return (function () {
//// [|return|];
//// [|return|];
//// [|return|];
////
//// if (false) {
//// [|return|] true;
//// }
//// [|th/**/row|] "Hello!";
//// })() || true;
//// }
////
//// throw 10;
////
//// var unusued = [1, 2, 3, 4].map(x => { throw 4 })
////
//// return;
//// return true;
//// throw false;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});
goTo.marker();
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});

View File

@@ -0,0 +1,58 @@
/// <reference path='fourslash.ts' />
////function f(a: number) {
//// try {
//// throw "Hello";
////
//// try {
//// throw 10;
//// }
//// catch (x) {
//// return 100;
//// }
//// finally {
//// throw 10;
//// }
//// }
//// catch (x) {
//// throw "Something";
//// }
//// finally {
//// throw "Also something";
//// }
//// if (a > 0) {
//// return (function () {
//// return;
//// return;
//// return;
////
//// if (false) {
//// return true;
//// }
//// throw "Hello!";
//// })() || true;
//// }
////
//// throw 10;
////
//// var unusued = [1, 2, 3, 4].map(x => { [|thr/**/ow|] 4 })
////
//// return;
//// return true;
//// throw false;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});
goTo.marker();
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});

View File

@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
////[|throw|] 100;
////
////try {
//// throw 0;
//// var x = () => { throw 0; };
////}
////catch (y) {
//// var x = () => { throw 0; };
//// [|throw|] 200;
////}
////finally {
//// [|throw|] 300;
////}
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});

View File

@@ -0,0 +1,31 @@
/// <reference path='fourslash.ts' />
////try {
//// [|throw|] 10;
////
//// try {
//// throw 10;
//// }
//// catch (x) {
//// [|throw|] 10;
//// }
//// finally {
//// [|throw|] 10;
//// }
////}
////finally {
//// [|throw|] 10;
////}
////
////[|throw|] 10;
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});

View File

@@ -0,0 +1,31 @@
/// <reference path='fourslash.ts' />
////try {
//// throw 10;
////
//// try {
//// [|throw|] 10;
//// }
//// catch (x) {
//// throw 10;
//// }
//// finally {
//// throw 10;
//// }
////}
////finally {
//// throw 10;
////}
////
////throw 10;
test.ranges().forEach(r => {
goTo.position(r.start);
test.ranges().forEach(range => {
verify.occurrencesAtPositionContains(range, false);
});
verify.occurrencesAtPositionCount(test.ranges().length);
});