diff --git a/src/services/services.ts b/src/services/services.ts index 4da51d7188b..3da71ea902a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2635,6 +2635,11 @@ module ts { return getReturnOccurrences(node.parent); } break; + case SyntaxKind.ThrowKeyword: + if (hasKind(node.parent, SyntaxKind.ThrowStatement)) { + return getThrowOccurrences(node.parent); + } + break; case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: @@ -2752,12 +2757,108 @@ module ts { } var keywords: Node[] = [] - forEachReturnStatement((func).body, returnStatement => { + forEachReturnStatement(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(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(node); + } + else if (node.kind === SyntaxKind.TryStatement) { + var 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 = parent; + + if (tryStatement.tryBlock === child && tryStatement.catchBlock) { + return child; + } + } + + child = parent; + } + + return undefined; + } function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { var keywords: Node[] = []; diff --git a/tests/cases/fourslash/getOccurrencesThrow.ts b/tests/cases/fourslash/getOccurrencesThrow.ts new file mode 100644 index 00000000000..c25551db92e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow.ts @@ -0,0 +1,58 @@ +/// + +////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); +}); diff --git a/tests/cases/fourslash/getOccurrencesThrow2.ts b/tests/cases/fourslash/getOccurrencesThrow2.ts new file mode 100644 index 00000000000..99e18020396 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow2.ts @@ -0,0 +1,58 @@ +/// + +////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); +}); diff --git a/tests/cases/fourslash/getOccurrencesThrow3.ts b/tests/cases/fourslash/getOccurrencesThrow3.ts new file mode 100644 index 00000000000..313d04b8e38 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow3.ts @@ -0,0 +1,58 @@ +/// + +////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); +}); diff --git a/tests/cases/fourslash/getOccurrencesThrow4.ts b/tests/cases/fourslash/getOccurrencesThrow4.ts new file mode 100644 index 00000000000..adf321526af --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow4.ts @@ -0,0 +1,58 @@ +/// + +////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); +}); diff --git a/tests/cases/fourslash/getOccurrencesThrow5.ts b/tests/cases/fourslash/getOccurrencesThrow5.ts new file mode 100644 index 00000000000..3e5ba4bca13 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow5.ts @@ -0,0 +1,58 @@ +/// + +////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); +}); diff --git a/tests/cases/fourslash/getOccurrencesThrow6.ts b/tests/cases/fourslash/getOccurrencesThrow6.ts new file mode 100644 index 00000000000..2769c9933db --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow6.ts @@ -0,0 +1,28 @@ +/// + +////[|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); +}); + diff --git a/tests/cases/fourslash/getOccurrencesThrow7.ts b/tests/cases/fourslash/getOccurrencesThrow7.ts new file mode 100644 index 00000000000..46e899a891b --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow7.ts @@ -0,0 +1,31 @@ +/// + +////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); +}); + diff --git a/tests/cases/fourslash/getOccurrencesThrow8.ts b/tests/cases/fourslash/getOccurrencesThrow8.ts new file mode 100644 index 00000000000..679e13c8d5a --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThrow8.ts @@ -0,0 +1,31 @@ +/// + +////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); +}); +