diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4e1b737b400..1e957cd2abe 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -483,6 +483,22 @@ module ts { nodeIsNestedInLabel(label: Identifier, requireIterationStatement: boolean, stopAtFunctionBoundary: boolean): ControlBlockContext; } + export function isKeyword(token: SyntaxKind): boolean { + return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword; + } + + export function isModifier(token: SyntaxKind): boolean { + switch (token) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.DeclareKeyword: + return true; + } + return false; + } + export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, version: string, isOpen: boolean = false): SourceFile { var file: SourceFile; var scanner: Scanner; @@ -853,23 +869,6 @@ module ts { return parseIdentifierName(); } - - function isKeyword(token: SyntaxKind): boolean { - return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword; - } - - function isModifier(token: SyntaxKind): boolean { - switch (token) { - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.DeclareKeyword: - return true; - } - return false; - } - function parseContextualModifier(t: SyntaxKind): boolean { return token === t && tryParse(() => { nextToken(); diff --git a/src/services/services.ts b/src/services/services.ts index 06e67415ea5..862ca387e86 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1314,12 +1314,13 @@ module ts { function isAnyFunction(node: Node): boolean { switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Method: case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Method: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - case SyntaxKind.ArrowFunction: + case SyntaxKind.Constructor: return true; } return false; @@ -1933,7 +1934,9 @@ module ts { current = child; continue outer; } - if (child.end > position) break; + if (child.end > position) { + break; + } } return current; } @@ -2160,13 +2163,143 @@ module ts { return undefined; } - if (node.kind !== SyntaxKind.Identifier && - !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && - !isNameOfExternalModuleImportOrDeclaration(node)) { + if (node.kind === SyntaxKind.Identifier || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) { + return getReferencesForNode(node, [sourceFile]); + } + + switch (node.kind) { + case SyntaxKind.TryKeyword: + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) { + return getTryCatchFinallyOccurrences(node.parent.parent); + } + break; + case SyntaxKind.SwitchKeyword: + if (hasKind(node.parent, SyntaxKind.SwitchStatement)) { + return getSwitchCaseDefaultOccurrences(node.parent); + } + break; + case SyntaxKind.CaseKeyword: + case SyntaxKind.DefaultKeyword: + if (hasKind(parent(parent(node)), SyntaxKind.SwitchStatement)) { + return getSwitchCaseDefaultOccurrences(node.parent.parent); + } + break; + case SyntaxKind.BreakKeyword: + if (hasKind(node.parent, SyntaxKind.BreakStatement)) { + return getBreakStatementOccurences(node.parent); + } + break; + } + + return undefined; + + function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { + var keywords: Node[] = []; + + pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); + + if (tryStatement.catchBlock) { + pushKeywordIf(keywords, tryStatement.catchBlock.getFirstToken(), SyntaxKind.CatchKeyword); + } + + if (tryStatement.finallyBlock) { + pushKeywordIf(keywords, tryStatement.finallyBlock.getFirstToken(), SyntaxKind.FinallyKeyword); + } + + return keywordsToReferenceEntries(keywords); + } + + function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { + var keywords: Node[] = []; + + pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); + + // Go through each clause in the switch statement, collecting the clause keywords. + forEach(switchStatement.clauses, clause => { + pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); + + // For each clause, also recursively traverse the statements where we can find analogous breaks. + forEachChild(clause, function aggregateBreakKeywords(node: Node): void { + switch (node.kind) { + case SyntaxKind.BreakStatement: + // If the break statement has a label, it cannot be part of a switch block. + if (!(node).label) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword); + } + // Fall through + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.SwitchStatement: + return; + } + + // Do not cross function boundaries. + if (!isAnyFunction(node)) { + forEachChild(node, aggregateBreakKeywords); + } + }); + }); + + return keywordsToReferenceEntries(keywords); + } + + function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ + // TODO (drosen): Deal with labeled statements. + if (breakStatement.label) { + return undefined; + } + + for (var owner = node.parent; owner; owner = owner.parent) { + switch (owner.kind) { + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + // TODO (drosen): Handle loops! + return undefined; + + case SyntaxKind.SwitchStatement: + return getSwitchCaseDefaultOccurrences(owner); + + default: + if (isAnyFunction(owner)) { + return undefined; + } + } + } + return undefined; } - return getReferencesForNode(node, [sourceFile]); + // returns true if 'node' is defined and has a matching 'kind'. + function hasKind(node: Node, kind: SyntaxKind) { + return !!(node && node.kind === kind); + } + + // Null-propagating 'parent' function. + function parent(node: Node): Node { + return node && node.parent; + } + + function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): void { + if (!token) { + return; + } + + if (contains(expected, token.kind)) { + keywordList.push(token); + } + } + + function keywordsToReferenceEntries(keywords: Node[]): ReferenceEntry[]{ + return map(keywords, keyword => + new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false) + ); + } } function getReferencesAtPosition(filename: string, position: number): ReferenceEntry[] { @@ -2284,13 +2417,13 @@ module ts { var container = getContainerNode(declarations[i]); if (scope && scope !== container) { - // Diffrent declarations have diffrent containers, bail out + // Different declarations have different containers, bail out return undefined; } if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { // This is a global variable and not an external module, any declaration defined - // withen this scope is visible outside the file + // within this scope is visible outside the file return undefined; } @@ -2957,7 +3090,7 @@ module ts { // ["// hack 1", "// ", "hack 1", undefined, "hack"] // // Here are the relevant capture groups: - // 0) The full match for hte entire regex. + // 0) The full match for the entire regex. // 1) The preamble to the message portion. // 2) The message portion. // 3...N) The descriptor that was matched - by index. 'undefined' for each diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts new file mode 100644 index 00000000000..a3d51e642d1 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts @@ -0,0 +1,29 @@ +/// + +////[|sw/*1*/itch|] (10) { +//// [|/*2*/case|] 1: +//// [|cas/*3*/e|] 2: +//// [|c/*4*/ase|] 4: +//// [|c/*5*/ase|] 8: +//// foo: switch (20) { +//// case 1: +//// case 2: +//// break; +//// default: +//// break foo; +//// } +//// [|cas/*6*/e|] 0xBEEF: +//// [|defa/*7*/ult|]: +//// [|bre/*9*/ak|]; +//// [|/*8*/case|] 16: +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(9); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +} diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts new file mode 100644 index 00000000000..a8c91f530ca --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts @@ -0,0 +1,29 @@ +/// + +////switch (10) { +//// case 1: +//// case 2: +//// case 4: +//// case 8: +//// foo: [|swi/*1*/tch|] (20) { +//// [|/*2*/case|] 1: +//// [|cas/*3*/e|] 2: +//// [|b/*4*/reak|]; +//// [|defaul/*5*/t|]: +//// break foo; +//// } +//// case 0xBEEF: +//// default: +//// break; +//// case 16: +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(5); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +} diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultBroken.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultBroken.ts new file mode 100644 index 00000000000..018280a4032 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultBroken.ts @@ -0,0 +1,57 @@ +/// + +////swi/*1*/tch(10) { +//// case 1: +//// case 2: +//// c/*2*/ase 4: +//// case 8: +//// case 0xBEEF: +//// de/*4*/fult: +//// break; +//// /*5*/cas 16: +//// c/*3*/ase 12: +//// function f() { +//// br/*11*/eak; +//// /*12*/break; +//// } +////} +//// +////sw/*6*/itch (10) { +//// de/*7*/fault +//// case 1: +//// case 2 +//// +//// c/*8*/ose 4: +//// case 8: +//// case 0xBEEF: +//// bre/*9*/ak; +//// case 16: +//// () => bre/*10*/ak; +////} + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + + switch (i) { + case 1: + case 2: + case 3: + verify.occurrencesAtPositionCount(8); + break; + case 4: + case 5: + case 8: + verify.occurrencesAtPositionCount(1); + break; + case 6: + case 7: + case 9: + verify.occurrencesAtPositionCount(8); + break; + case 10: + case 11: + case 12: + verify.occurrencesAtPositionCount(0); + break; + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultNegatives.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultNegatives.ts new file mode 100644 index 00000000000..cfb70b0c11a --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefaultNegatives.ts @@ -0,0 +1,25 @@ +/// + +////switch/*1*/ (10) { +//// case/*2*/ 1: +//// case/*3*/ 2: +//// case/*4*/ 4: +//// case/*5*/ 8: +//// foo: switch/*6*/ (20) { +//// case/*7*/ 1: +//// case/*8*/ 2: +//// break/*9*/; +//// default/*10*/: +//// break foo; +//// } +//// case/*11*/ 0xBEEF: +//// default/*12*/: +//// break/*13*/; +//// case 16/*14*/: +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(0); +} diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts new file mode 100644 index 00000000000..9037b6f8029 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinally.ts @@ -0,0 +1,27 @@ +/// + +/////*1*/[|try|] { +//// try { +//// } +//// catch (x) { +//// } +//// +//// try { +//// } +//// finally { +//// } +////} +////[|cat/*2*/ch|] (e) { +////} +////[|fina/*3*/lly|] { +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(3); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +} diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinally2.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinally2.ts new file mode 100644 index 00000000000..5d71b133c27 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinally2.ts @@ -0,0 +1,27 @@ +/// + +////try { +//// [|t/*1*/r/*2*/y|] { +//// } +//// [|c/*3*/atch|] (x) { +//// } +//// +//// try { +//// } +//// finally { +//// } +////} +////catch (e) { +////} +////finally { +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(2); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinally3.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinally3.ts new file mode 100644 index 00000000000..f2a79369364 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinally3.ts @@ -0,0 +1,27 @@ +/// + +////try { +//// try { +//// } +//// catch (x) { +//// } +//// +//// [|t/*1*/r/*2*/y|] { +//// } +//// [|finall/*3*/y|] { +//// } +////} +////catch (e) { +////} +////finally { +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(2); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinallyBroken.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinallyBroken.ts new file mode 100644 index 00000000000..c866ed96398 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinallyBroken.ts @@ -0,0 +1,55 @@ +/// + +////t /*1*/ry { +//// t/*2*/ry { +//// } +//// ctch (x) { +//// } +//// +//// tr { +//// } +//// fin/*3*/ally { +//// } +////} +////c/*4*/atch (e) { +////} +////f/*5*/inally { +////} +//// +////// Missing catch variable +////t/*6*/ry { +////} +////catc/*7*/h { +////} +/////*8*/finally { +////} +//// +////// Missing try entirely +////cat/*9*/ch (x) { +////} +////final/*10*/ly { +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + + switch (i) { + case 1: + case 2: + case 3: + verify.occurrencesAtPositionCount(1); + break; + case 4: + case 5: + case 9: + case 10: + verify.occurrencesAtPositionCount(2); + break; + case 6: + case 7: + case 8: + verify.occurrencesAtPositionCount(3); + break; + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesTryCatchFinallyNegatives.ts b/tests/cases/fourslash/getOccurrencesTryCatchFinallyNegatives.ts new file mode 100644 index 00000000000..d5889ff84e9 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesTryCatchFinallyNegatives.ts @@ -0,0 +1,23 @@ +/// + +////try/*1*/ { +//// try/*2*/ { +//// } +//// catch/*3*/ (x) { +//// } +//// +//// try/*4*/ { +//// } +//// finally/*5*/ {/*8*/ +//// } +////} +////catch/*6*/ (e) { +////} +////finally/*7*/ { +////} + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(0); +} \ No newline at end of file