Skip to content

Commit 0c0f4b8

Browse files
author
Andy
authored
Simplify documentHighlights (microsoft#20091)
1 parent cc0f923 commit 0c0f4b8

4 files changed

Lines changed: 78 additions & 125 deletions

File tree

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,6 +4724,10 @@ namespace ts {
47244724
return node.kind === SyntaxKind.BreakStatement;
47254725
}
47264726

4727+
export function isBreakOrContinueStatement(node: Node): node is BreakOrContinueStatement {
4728+
return node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement;
4729+
}
4730+
47274731
export function isReturnStatement(node: Node): node is ReturnStatement {
47284732
return node.kind === SyntaxKind.ReturnStatement;
47294733
}

src/services/documentHighlights.ts

Lines changed: 72 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -53,95 +53,48 @@ namespace ts.DocumentHighlights {
5353
return [{ fileName: sourceFile.fileName, highlightSpans }];
5454
}
5555

56-
// returns true if 'node' is defined and has a matching 'kind'.
57-
function hasKind(node: Node, kind: SyntaxKind) {
58-
return node !== undefined && node.kind === kind;
59-
}
60-
61-
// Null-propagating 'parent' function.
62-
function parent(node: Node): Node {
63-
return node && node.parent;
64-
}
65-
66-
function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] {
67-
if (!node) {
68-
return undefined;
69-
}
70-
56+
function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined {
7157
switch (node.kind) {
7258
case SyntaxKind.IfKeyword:
7359
case SyntaxKind.ElseKeyword:
74-
if (hasKind(node.parent, SyntaxKind.IfStatement)) {
75-
return getIfElseOccurrences(<IfStatement>node.parent, sourceFile);
76-
}
77-
break;
60+
return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined;
7861
case SyntaxKind.ReturnKeyword:
79-
if (hasKind(node.parent, SyntaxKind.ReturnStatement)) {
80-
return highlightSpans(getReturnOccurrences(<ReturnStatement>node.parent));
81-
}
82-
break;
62+
return useParent(node.parent, isReturnStatement, getReturnOccurrences);
8363
case SyntaxKind.ThrowKeyword:
84-
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
85-
return highlightSpans(getThrowOccurrences(<ThrowStatement>node.parent));
86-
}
87-
break;
64+
return useParent(node.parent, isThrowStatement, getThrowOccurrences);
8865
case SyntaxKind.TryKeyword:
8966
case SyntaxKind.CatchKeyword:
9067
case SyntaxKind.FinallyKeyword:
91-
const tryStatement = node.kind === SyntaxKind.CatchKeyword ? parent(parent(node)) : parent(node);
92-
if (hasKind(tryStatement, SyntaxKind.TryStatement)) {
93-
return highlightSpans(getTryCatchFinallyOccurrences(<TryStatement>tryStatement, sourceFile));
94-
}
95-
break;
68+
const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent;
69+
return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences);
9670
case SyntaxKind.SwitchKeyword:
97-
if (hasKind(node.parent, SyntaxKind.SwitchStatement)) {
98-
return highlightSpans(getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent));
99-
}
100-
break;
71+
return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
10172
case SyntaxKind.CaseKeyword:
10273
case SyntaxKind.DefaultKeyword:
103-
if (hasKind(parent(parent(parent(node))), SyntaxKind.SwitchStatement)) {
104-
return highlightSpans(getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent.parent.parent));
105-
}
106-
break;
74+
return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
10775
case SyntaxKind.BreakKeyword:
10876
case SyntaxKind.ContinueKeyword:
109-
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
110-
return highlightSpans(getBreakOrContinueStatementOccurrences(<BreakOrContinueStatement>node.parent));
111-
}
112-
break;
77+
return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences);
11378
case SyntaxKind.ForKeyword:
114-
if (hasKind(node.parent, SyntaxKind.ForStatement) ||
115-
hasKind(node.parent, SyntaxKind.ForInStatement) ||
116-
hasKind(node.parent, SyntaxKind.ForOfStatement)) {
117-
return highlightSpans(getLoopBreakContinueOccurrences(<IterationStatement>node.parent));
118-
}
119-
break;
12079
case SyntaxKind.WhileKeyword:
12180
case SyntaxKind.DoKeyword:
122-
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
123-
return highlightSpans(getLoopBreakContinueOccurrences(<IterationStatement>node.parent));
124-
}
125-
break;
81+
return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences);
12682
case SyntaxKind.ConstructorKeyword:
127-
if (hasKind(node.parent, SyntaxKind.Constructor)) {
128-
return highlightSpans(getConstructorOccurrences(<ConstructorDeclaration>node.parent));
129-
}
130-
break;
83+
return useParent(node.parent, isConstructorDeclaration, getConstructorOccurrences);
13184
case SyntaxKind.GetKeyword:
13285
case SyntaxKind.SetKeyword:
133-
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
134-
return highlightSpans(getGetAndSetOccurrences(<AccessorDeclaration>node.parent));
135-
}
136-
break;
86+
return useParent(node.parent, isAccessor, getGetAndSetOccurrences);
13787
default:
138-
if (isModifierKind(node.kind) && node.parent &&
139-
(isDeclaration(node.parent) || node.parent.kind === SyntaxKind.VariableStatement)) {
140-
return highlightSpans(getModifierOccurrences(node.kind, node.parent));
141-
}
88+
return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent))
89+
? highlightSpans(getModifierOccurrences(node.kind, node.parent))
90+
: undefined;
14291
}
14392

144-
function highlightSpans(nodes: Node[]): HighlightSpan[] {
93+
function useParent<T extends Node>(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => Node[] | undefined): HighlightSpan[] | undefined {
94+
return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined;
95+
}
96+
97+
function highlightSpans(nodes: Node[] | undefined): HighlightSpan[] | undefined {
14598
return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile));
14699
}
147100
}
@@ -156,23 +109,21 @@ namespace ts.DocumentHighlights {
156109
return statementAccumulator;
157110

158111
function aggregate(node: Node): void {
159-
if (node.kind === SyntaxKind.ThrowStatement) {
160-
statementAccumulator.push(<ThrowStatement>node);
112+
if (isThrowStatement(node)) {
113+
statementAccumulator.push(node);
161114
}
162-
else if (node.kind === SyntaxKind.TryStatement) {
163-
const tryStatement = <TryStatement>node;
164-
165-
if (tryStatement.catchClause) {
166-
aggregate(tryStatement.catchClause);
115+
else if (isTryStatement(node)) {
116+
if (node.catchClause) {
117+
aggregate(node.catchClause);
167118
}
168119
else {
169120
// Exceptions thrown within a try block lacking a catch clause
170121
// are "owned" in the current context.
171-
aggregate(tryStatement.tryBlock);
122+
aggregate(node.tryBlock);
172123
}
173124

174-
if (tryStatement.finallyBlock) {
175-
aggregate(tryStatement.finallyBlock);
125+
if (node.finallyBlock) {
126+
aggregate(node.finallyBlock);
176127
}
177128
}
178129
// Do not cross function boundaries.
@@ -236,32 +187,25 @@ namespace ts.DocumentHighlights {
236187
}
237188

238189
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node {
239-
for (let node = statement.parent; node; node = node.parent) {
190+
return findAncestor(statement, node => {
240191
switch (node.kind) {
241192
case SyntaxKind.SwitchStatement:
242193
if (statement.kind === SyntaxKind.ContinueStatement) {
243-
continue;
194+
return false;
244195
}
245196
// falls through
246197
case SyntaxKind.ForStatement:
247198
case SyntaxKind.ForInStatement:
248199
case SyntaxKind.ForOfStatement:
249200
case SyntaxKind.WhileStatement:
250201
case SyntaxKind.DoStatement:
251-
if (!statement.label || isLabeledBy(node, statement.label.text)) {
252-
return node;
253-
}
254-
break;
202+
return !statement.label || isLabeledBy(node, statement.label.text);
255203
default:
256204
// Don't cross function boundaries.
257-
if (isFunctionLike(node)) {
258-
return undefined;
259-
}
260-
break;
205+
// TODO: GH#20090
206+
return (isFunctionLike(node) && "quit") as false | "quit";
261207
}
262-
}
263-
264-
return undefined;
208+
});
265209
}
266210

267211
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): Node[] {
@@ -494,16 +438,14 @@ namespace ts.DocumentHighlights {
494438
return keywords;
495439
}
496440

497-
function getReturnOccurrences(returnStatement: ReturnStatement): Node[] {
441+
function getReturnOccurrences(returnStatement: ReturnStatement): Node[] | undefined {
498442
const func = <FunctionLikeDeclaration>getContainingFunction(returnStatement);
499-
500-
// If we didn't find a containing function with a block body, bail out.
501-
if (!(func && hasKind(func.body, SyntaxKind.Block))) {
443+
if (!func) {
502444
return undefined;
503445
}
504446

505447
const keywords: Node[] = [];
506-
forEachReturnStatement(<Block>func.body, returnStatement => {
448+
forEachReturnStatement(cast(func.body, isBlock), returnStatement => {
507449
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
508450
});
509451

@@ -516,32 +458,7 @@ namespace ts.DocumentHighlights {
516458
}
517459

518460
function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] {
519-
const keywords: Node[] = [];
520-
521-
// Traverse upwards through all parent if-statements linked by their else-branches.
522-
while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (<IfStatement>ifStatement.parent).elseStatement === ifStatement) {
523-
ifStatement = <IfStatement>ifStatement.parent;
524-
}
525-
526-
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
527-
while (ifStatement) {
528-
const children = ifStatement.getChildren();
529-
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
530-
531-
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
532-
for (let i = children.length - 1; i >= 0; i--) {
533-
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
534-
break;
535-
}
536-
}
537-
538-
if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) {
539-
break;
540-
}
541-
542-
ifStatement = <IfStatement>ifStatement.elseStatement;
543-
}
544-
461+
const keywords = getIfElseKeywords(ifStatement, sourceFile);
545462
const result: HighlightSpan[] = [];
546463

547464
// We'd like to highlight else/ifs together if they are only separated by whitespace
@@ -551,17 +468,17 @@ namespace ts.DocumentHighlights {
551468
const elseKeyword = keywords[i];
552469
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
553470

554-
let shouldCombindElseAndIf = true;
471+
let shouldCombineElseAndIf = true;
555472

556473
// Avoid recalculating getStart() by iterating backwards.
557-
for (let j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) {
474+
for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) {
558475
if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
559-
shouldCombindElseAndIf = false;
476+
shouldCombineElseAndIf = false;
560477
break;
561478
}
562479
}
563480

564-
if (shouldCombindElseAndIf) {
481+
if (shouldCombineElseAndIf) {
565482
result.push({
566483
fileName: sourceFile.fileName,
567484
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
@@ -579,6 +496,36 @@ namespace ts.DocumentHighlights {
579496
return result;
580497
}
581498

499+
function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] {
500+
const keywords: Node[] = [];
501+
502+
// Traverse upwards through all parent if-statements linked by their else-branches.
503+
while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) {
504+
ifStatement = ifStatement.parent;
505+
}
506+
507+
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
508+
while (true) {
509+
const children = ifStatement.getChildren(sourceFile);
510+
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
511+
512+
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
513+
for (let i = children.length - 1; i >= 0; i--) {
514+
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
515+
break;
516+
}
517+
}
518+
519+
if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) {
520+
break;
521+
}
522+
523+
ifStatement = ifStatement.elseStatement;
524+
}
525+
526+
return keywords;
527+
}
528+
582529
/**
583530
* Whether or not a 'node' is preceded by a label of the given string.
584531
* Note: 'node' cannot be a SourceFile.

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2992,6 +2992,7 @@ declare namespace ts {
29922992
function isForOfStatement(node: Node): node is ForOfStatement;
29932993
function isContinueStatement(node: Node): node is ContinueStatement;
29942994
function isBreakStatement(node: Node): node is BreakStatement;
2995+
function isBreakOrContinueStatement(node: Node): node is BreakOrContinueStatement;
29952996
function isReturnStatement(node: Node): node is ReturnStatement;
29962997
function isWithStatement(node: Node): node is WithStatement;
29972998
function isSwitchStatement(node: Node): node is SwitchStatement;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3045,6 +3045,7 @@ declare namespace ts {
30453045
function isForOfStatement(node: Node): node is ForOfStatement;
30463046
function isContinueStatement(node: Node): node is ContinueStatement;
30473047
function isBreakStatement(node: Node): node is BreakStatement;
3048+
function isBreakOrContinueStatement(node: Node): node is BreakOrContinueStatement;
30483049
function isReturnStatement(node: Node): node is ReturnStatement;
30493050
function isWithStatement(node: Node): node is WithStatement;
30503051
function isSwitchStatement(node: Node): node is SwitchStatement;

0 commit comments

Comments
 (0)