diff --git a/src/services/services.ts b/src/services/services.ts index 862ca387e8..e5bdd0bf75 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2168,6 +2168,12 @@ module ts { } switch (node.kind) { + case SyntaxKind.IfKeyword: + case SyntaxKind.ElseKeyword: + if (hasKind(node.parent, SyntaxKind.IfStatement)) { + return getIfElseOccurrences(node.parent); + } + break; case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: @@ -2195,6 +2201,66 @@ module ts { return undefined; + function getIfElseOccurrences(ifStatement: IfStatement): ReferenceEntry[] { + var keywords: Node[] = []; + + // Traverse upwards through all parent if-statements linked by their else-branches. + while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (ifStatement.parent).elseStatement === ifStatement) { + ifStatement = ifStatement.parent; + } + + // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. + while (ifStatement) { + var children = ifStatement.getChildren(); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); + + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (var i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { + break; + } + } + + if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) { + break + } + + ifStatement = ifStatement.elseStatement; + } + + var result: ReferenceEntry[] = []; + + // We'd like to highlight else/ifs together if they are only separated by whitespace + // (i.e. the keywords are separated by no comments, no newlines). + for (var i = 0; i < keywords.length; i++) { + if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { + var elseKeyword = keywords[i]; + var ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. + + var shouldHighlightNextKeyword = true; + + // Avoid recalculating getStart() by iterating backwards. + for (var j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) { + if (!isWhiteSpace(sourceFile.text.charCodeAt(j))) { + shouldHighlightNextKeyword = false; + break; + } + } + + if (shouldHighlightNextKeyword) { + result.push(new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), /* isWriteAccess */ false)); + i++; // skip the next keyword + continue; + } + } + + // Ordinary case: just highlight the keyword. + result.push(keywordToReferenceEntry(keywords[i])); + } + + return result; + } + function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { var keywords: Node[] = []; @@ -2208,7 +2274,7 @@ module ts { pushKeywordIf(keywords, tryStatement.finallyBlock.getFirstToken(), SyntaxKind.FinallyKeyword); } - return keywordsToReferenceEntries(keywords); + return map(keywords, keywordToReferenceEntry); } function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { @@ -2244,7 +2310,7 @@ module ts { }); }); - return keywordsToReferenceEntries(keywords); + return map(keywords, keywordToReferenceEntry); } function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ @@ -2285,20 +2351,17 @@ module ts { return node && node.parent; } - function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): void { - if (!token) { - return; + function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean { + if (token && contains(expected, token.kind)) { + keywordList.push(token); + return true; } - if (contains(expected, token.kind)) { - keywordList.push(token); - } + return false; } - function keywordsToReferenceEntries(keywords: Node[]): ReferenceEntry[]{ - return map(keywords, keyword => - new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false) - ); + function keywordToReferenceEntry(keyword: Node): ReferenceEntry { + return new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false); } } diff --git a/tests/cases/fourslash/getOccurrencesIfElse.ts b/tests/cases/fourslash/getOccurrencesIfElse.ts new file mode 100644 index 0000000000..96c70d404a --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse.ts @@ -0,0 +1,36 @@ +/// + +////[|if|] (true) { +//// if (false) { +//// } +//// else { +//// } +//// if (true) { +//// } +//// else { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////[|else i/**/f|] (null) { +////} +////[|else|] /* whar garbl */ [|if|] (undefined) { +////} +////[|else|] +////[|if|] (false) { +////} +////[|else|] { } + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse2.ts b/tests/cases/fourslash/getOccurrencesIfElse2.ts new file mode 100644 index 0000000000..012e7a96ef --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse2.ts @@ -0,0 +1,32 @@ +/// + +////if (true) { +//// [|if|] (false) { +//// } +//// [|else|]{ +//// } +//// if (true) { +//// } +//// else { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse3.ts b/tests/cases/fourslash/getOccurrencesIfElse3.ts new file mode 100644 index 0000000000..b0ca361564 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse3.ts @@ -0,0 +1,32 @@ +/// + +////if (true) { +//// if (false) { +//// } +//// else { +//// } +//// [|if|] (true) { +//// } +//// [|else|] { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse4.ts b/tests/cases/fourslash/getOccurrencesIfElse4.ts new file mode 100644 index 0000000000..6674625274 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse4.ts @@ -0,0 +1,30 @@ +/// + +////if (true) { +//// if (false) { +//// } +//// else { +//// } +//// if (true) { +//// } +//// else { +//// /*1*/if (false) +//// /*2*/i/*3*/f (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + + verify.occurrencesAtPositionCount(1); +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElseBroken.ts b/tests/cases/fourslash/getOccurrencesIfElseBroken.ts new file mode 100644 index 0000000000..a9e1e94f42 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElseBroken.ts @@ -0,0 +1,24 @@ +/// + + +////[|if|] (true) { +//// var x = 1; +////} +////[|else if|] () +////[|else if|] +////[|else|] /* whar garbl */ [|if|] (i/**/f (true) { } else { }) +////else + +// It would be nice if in the future, +// We could include that last 'else'. + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +verify.occurrencesAtPositionCount(2); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts new file mode 100644 index 0000000000..601eba63c6 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts @@ -0,0 +1,29 @@ +/// + +////if/*1*/ (true) { +//// if/*2*/ (false) { +//// } +//// else/*3*/ { +//// } +//// if/*4*/ (true) { +//// } +//// else/*5*/ { +//// if/*6*/ (false) +//// if/*7*/ (true) +//// var x = undefined; +//// } +////} +////else/*8*/ if (null) { +////} +////else/*9*/ /* whar garbl */ if/*10*/ (undefined) { +////} +////else/*11*/ +////if/*12*/ (false) { +////} +////else/*13*/ { } + + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName) + verify.occurrencesAtPositionCount(0); +});