• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    __String, arrayFrom, arrayToMultiMap, Block, BreakOrContinueStatement, CancellationToken, CaseClause, cast,
3    concatenate, ConstructorDeclaration, contains, createGetCanonicalFileName, createTextSpanFromBounds,
4    createTextSpanFromNode, Debug, DefaultClause, find, FindAllReferences, findAncestor, findChildOfKind, findModifier,
5    forEach, forEachChild, forEachReturnStatement, FunctionDeclaration, FunctionLikeDeclaration, getContainingFunction,
6    getTouchingPropertyName, HighlightSpan, HighlightSpanKind, IfStatement, isAccessor, isAwaitExpression, isBlock,
7    isBreakOrContinueStatement, isCaseClause, isClassDeclaration, isClassLike, isConstructorDeclaration, isDeclaration,
8    isDefaultClause, isFunctionBlock, isFunctionLike, isIfStatement, isInterfaceDeclaration, isIterationStatement,
9    isJsxClosingElement, isJsxOpeningElement, isLabeledStatement, isModifierKind, isModuleDeclaration,
10    isReturnStatement, isSwitchStatement, isThrowStatement, isTryStatement, isTypeAliasDeclaration, isTypeNode,
11    isVariableStatement, isWhiteSpaceSingleLine, isYieldExpression, IterationStatement, mapDefined, MethodDeclaration,
12    Modifier, ModifierFlags, modifierToFlag, ModuleBlock, Node, ObjectLiteralExpression, ObjectTypeDeclaration, Program,
13    Push, ReturnStatement, Set, SourceFile, SwitchStatement, SyntaxKind, ThrowStatement, toArray, toPath, TryStatement,
14} from "./_namespaces/ts";
15
16export interface DocumentHighlights {
17    fileName: string;
18    highlightSpans: HighlightSpan[];
19}
20
21/** @internal */
22export namespace DocumentHighlights {
23    export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
24        const node = getTouchingPropertyName(sourceFile, position);
25
26        if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) {
27            // For a JSX element, just highlight the matching tag, not all references.
28            const { openingElement, closingElement } = node.parent.parent;
29            const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile));
30            return [{ fileName: sourceFile.fileName, highlightSpans }];
31        }
32
33        return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile);
34    }
35
36    function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan {
37        return {
38            fileName: sourceFile.fileName,
39            textSpan: createTextSpanFromNode(node, sourceFile),
40            kind: HighlightSpanKind.none
41        };
42    }
43
44    function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
45        const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName));
46        const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet);
47        if (!referenceEntries) return undefined;
48        const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span);
49        const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames());
50        return mapDefined(arrayFrom(map.entries()), ([fileName, highlightSpans]) => {
51            if (!sourceFilesSet.has(fileName)) {
52                if (!program.redirectTargetsMap.has(toPath(fileName, program.getCurrentDirectory(), getCanonicalFileName))) {
53                    return undefined;
54                }
55                const redirectTarget = program.getSourceFile(fileName);
56                const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!;
57                fileName = redirect.fileName;
58                Debug.assert(sourceFilesSet.has(fileName));
59            }
60            return { fileName, highlightSpans };
61        });
62    }
63
64    function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined {
65        const highlightSpans = getHighlightSpans(node, sourceFile);
66        return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }];
67    }
68
69    function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined {
70        switch (node.kind) {
71            case SyntaxKind.IfKeyword:
72            case SyntaxKind.ElseKeyword:
73                return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined;
74            case SyntaxKind.ReturnKeyword:
75                return useParent(node.parent, isReturnStatement, getReturnOccurrences);
76            case SyntaxKind.ThrowKeyword:
77                return useParent(node.parent, isThrowStatement, getThrowOccurrences);
78            case SyntaxKind.TryKeyword:
79            case SyntaxKind.CatchKeyword:
80            case SyntaxKind.FinallyKeyword:
81                const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent;
82                return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences);
83            case SyntaxKind.SwitchKeyword:
84                return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
85            case SyntaxKind.CaseKeyword:
86            case SyntaxKind.DefaultKeyword: {
87                if (isDefaultClause(node.parent) || isCaseClause(node.parent)) {
88                    return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
89                }
90                return undefined;
91            }
92            case SyntaxKind.BreakKeyword:
93            case SyntaxKind.ContinueKeyword:
94                return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences);
95            case SyntaxKind.ForKeyword:
96            case SyntaxKind.WhileKeyword:
97            case SyntaxKind.DoKeyword:
98                return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences);
99            case SyntaxKind.ConstructorKeyword:
100                return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]);
101            case SyntaxKind.GetKeyword:
102            case SyntaxKind.SetKeyword:
103                return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]);
104            case SyntaxKind.AwaitKeyword:
105                return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences);
106            case SyntaxKind.AsyncKeyword:
107                return highlightSpans(getAsyncAndAwaitOccurrences(node));
108            case SyntaxKind.YieldKeyword:
109                return highlightSpans(getYieldOccurrences(node));
110            case SyntaxKind.InKeyword:
111                return undefined;
112            default:
113                return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent))
114                    ? highlightSpans(getModifierOccurrences(node.kind, node.parent))
115                    : undefined;
116        }
117
118        function getFromAllDeclarations<T extends Node>(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined {
119            return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d =>
120                nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined));
121        }
122
123        function useParent<T extends Node>(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined {
124            return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined;
125        }
126
127        function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined {
128            return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile));
129        }
130    }
131
132    /**
133     * Aggregates all throw-statements within this node *without* crossing
134     * into function boundaries and try-blocks with catch-clauses.
135     */
136    function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined {
137        if (isThrowStatement(node)) {
138            return [node];
139        }
140        else if (isTryStatement(node)) {
141            // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context.
142            return concatenate(
143                node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock),
144                node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock));
145        }
146        // Do not cross function boundaries.
147        return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements);
148    }
149
150    /**
151     * For lack of a better name, this function takes a throw statement and returns the
152     * nearest ancestor that is a try-block (whose try statement has a catch clause),
153     * function-block, or source file.
154     */
155    function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined {
156        let child: Node = throwStatement;
157
158        while (child.parent) {
159            const parent = child.parent;
160
161            if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) {
162                return parent;
163            }
164
165            // A throw-statement is only owned by a try-statement if the try-statement has
166            // a catch clause, and if the throw-statement occurs within the try block.
167            if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) {
168                return child;
169            }
170
171            child = parent;
172        }
173
174        return undefined;
175    }
176
177    function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined {
178        return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements);
179    }
180
181    function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
182        const result: T[] = [];
183        node.forEachChild(child => {
184            const value = cb(child);
185            if (value !== undefined) {
186                result.push(...toArray(value));
187            }
188        });
189        return result;
190    }
191
192    function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
193        const actualOwner = getBreakOrContinueOwner(statement);
194        return !!actualOwner && actualOwner === owner;
195    }
196
197    function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined {
198        return findAncestor(statement, node => {
199            switch (node.kind) {
200                case SyntaxKind.SwitchStatement:
201                    if (statement.kind === SyntaxKind.ContinueStatement) {
202                        return false;
203                    }
204                    // falls through
205
206                case SyntaxKind.ForStatement:
207                case SyntaxKind.ForInStatement:
208                case SyntaxKind.ForOfStatement:
209                case SyntaxKind.WhileStatement:
210                case SyntaxKind.DoStatement:
211                    return !statement.label || isLabeledBy(node, statement.label.escapedText);
212                default:
213                    // Don't cross function boundaries.
214                    // TODO: GH#20090
215                    return isFunctionLike(node) && "quit";
216            }
217        });
218    }
219
220    function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] {
221        return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier));
222    }
223
224    function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined {
225        // Types of node whose children might have modifiers.
226        const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression;
227        switch (container.kind) {
228            case SyntaxKind.ModuleBlock:
229            case SyntaxKind.SourceFile:
230            case SyntaxKind.Block:
231            case SyntaxKind.CaseClause:
232            case SyntaxKind.DefaultClause:
233                // Container is either a class declaration or the declaration is a classDeclaration
234                if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) {
235                    return [...declaration.members, declaration];
236                }
237                else {
238                    return container.statements;
239                }
240            case SyntaxKind.Constructor:
241            case SyntaxKind.MethodDeclaration:
242            case SyntaxKind.FunctionDeclaration:
243                return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])];
244            case SyntaxKind.ClassDeclaration:
245            case SyntaxKind.ClassExpression:
246            case SyntaxKind.StructDeclaration:
247            case SyntaxKind.InterfaceDeclaration:
248            case SyntaxKind.TypeLiteral:
249                const nodes = container.members;
250
251                // If we're an accessibility modifier, we're in an instance member and should search
252                // the constructor's parameter list for instance members as well.
253                if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) {
254                    const constructor = find(container.members, isConstructorDeclaration);
255                    if (constructor) {
256                        return [...nodes, ...constructor.parameters];
257                    }
258                }
259                else if (modifierFlag & ModifierFlags.Abstract) {
260                    return [...nodes, container];
261                }
262                return nodes;
263
264            // Syntactically invalid positions that the parser might produce anyway
265            case SyntaxKind.ObjectLiteralExpression:
266                return undefined;
267
268            default:
269                Debug.assertNever(container, "Invalid container kind.");
270        }
271    }
272
273    function pushKeywordIf(keywordList: Push<Node>, token: Node | undefined, ...expected: SyntaxKind[]): boolean {
274        if (token && contains(expected, token.kind)) {
275            keywordList.push(token);
276            return true;
277        }
278
279        return false;
280    }
281
282    function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] {
283        const keywords: Node[] = [];
284
285        if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
286            // If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
287            if (loopNode.kind === SyntaxKind.DoStatement) {
288                const loopTokens = loopNode.getChildren();
289
290                for (let i = loopTokens.length - 1; i >= 0; i--) {
291                    if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
292                        break;
293                    }
294                }
295            }
296        }
297
298        forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => {
299            if (ownsBreakOrContinueStatement(loopNode, statement)) {
300                pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
301            }
302        });
303
304        return keywords;
305    }
306
307    function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined {
308        const owner = getBreakOrContinueOwner(breakOrContinueStatement);
309
310        if (owner) {
311            switch (owner.kind) {
312                case SyntaxKind.ForStatement:
313                case SyntaxKind.ForInStatement:
314                case SyntaxKind.ForOfStatement:
315                case SyntaxKind.DoStatement:
316                case SyntaxKind.WhileStatement:
317                    return getLoopBreakContinueOccurrences(owner as IterationStatement);
318                case SyntaxKind.SwitchStatement:
319                    return getSwitchCaseDefaultOccurrences(owner as SwitchStatement);
320
321            }
322        }
323
324        return undefined;
325    }
326
327    function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] {
328        const keywords: Node[] = [];
329
330        pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
331
332        // Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
333        forEach(switchStatement.caseBlock.clauses, clause => {
334            pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
335
336            forEach(aggregateAllBreakAndContinueStatements(clause), statement => {
337                if (ownsBreakOrContinueStatement(switchStatement, statement)) {
338                    pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
339                }
340            });
341        });
342
343        return keywords;
344    }
345
346    function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] {
347        const keywords: Node[] = [];
348
349        pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
350
351        if (tryStatement.catchClause) {
352            pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
353        }
354
355        if (tryStatement.finallyBlock) {
356            const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!;
357            pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
358        }
359
360        return keywords;
361    }
362
363    function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined {
364        const owner = getThrowStatementOwner(throwStatement);
365
366        if (!owner) {
367            return undefined;
368        }
369
370        const keywords: Node[] = [];
371
372        forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
373            keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
374        });
375
376        // If the "owner" is a function, then we equate 'return' and 'throw' statements in their
377        // ability to "jump out" of the function, and include occurrences for both.
378        if (isFunctionBlock(owner)) {
379            forEachReturnStatement(owner as Block, returnStatement => {
380                keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
381            });
382        }
383
384        return keywords;
385    }
386
387    function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined {
388        const func = getContainingFunction(returnStatement) as FunctionLikeDeclaration;
389        if (!func) {
390            return undefined;
391        }
392
393        const keywords: Node[] = [];
394        forEachReturnStatement(cast(func.body, isBlock), returnStatement => {
395            keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
396        });
397
398        // Include 'throw' statements that do not occur within a try block.
399        forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => {
400            keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
401        });
402
403        return keywords;
404    }
405
406    function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined {
407        const func = getContainingFunction(node) as FunctionLikeDeclaration;
408        if (!func) {
409            return undefined;
410        }
411
412        const keywords: Node[] = [];
413
414        if (func.modifiers) {
415            func.modifiers.forEach(modifier => {
416                pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword);
417            });
418        }
419
420        forEachChild(func, child => {
421            traverseWithoutCrossingFunction(child, node => {
422                if (isAwaitExpression(node)) {
423                    pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword);
424                }
425            });
426        });
427
428
429        return keywords;
430    }
431
432    function getYieldOccurrences(node: Node): Node[] | undefined {
433        const func = getContainingFunction(node) as FunctionDeclaration;
434        if (!func) {
435            return undefined;
436        }
437
438        const keywords: Node[] = [];
439
440        forEachChild(func, child => {
441            traverseWithoutCrossingFunction(child, node => {
442                if (isYieldExpression(node)) {
443                    pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword);
444                }
445            });
446        });
447
448        return keywords;
449    }
450
451    // Do not cross function/class/interface/module/type boundaries.
452    function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) {
453        cb(node);
454        if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) {
455            forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
456        }
457    }
458
459    function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] {
460        const keywords = getIfElseKeywords(ifStatement, sourceFile);
461        const result: HighlightSpan[] = [];
462
463        // We'd like to highlight else/ifs together if they are only separated by whitespace
464        // (i.e. the keywords are separated by no comments, no newlines).
465        for (let i = 0; i < keywords.length; i++) {
466            if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
467                const elseKeyword = keywords[i];
468                const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
469
470                let shouldCombineElseAndIf = true;
471
472                // Avoid recalculating getStart() by iterating backwards.
473                for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) {
474                    if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
475                        shouldCombineElseAndIf = false;
476                        break;
477                    }
478                }
479
480                if (shouldCombineElseAndIf) {
481                    result.push({
482                        fileName: sourceFile.fileName,
483                        textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
484                        kind: HighlightSpanKind.reference
485                    });
486                    i++; // skip the next keyword
487                    continue;
488                }
489            }
490
491            // Ordinary case: just highlight the keyword.
492            result.push(getHighlightSpanForNode(keywords[i], sourceFile));
493        }
494
495        return result;
496    }
497
498    function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] {
499        const keywords: Node[] = [];
500
501        // Traverse upwards through all parent if-statements linked by their else-branches.
502        while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) {
503            ifStatement = ifStatement.parent;
504        }
505
506        // Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
507        while (true) {
508            const children = ifStatement.getChildren(sourceFile);
509            pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
510
511            // Generally the 'else' keyword is second-to-last, so we traverse backwards.
512            for (let i = children.length - 1; i >= 0; i--) {
513                if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
514                    break;
515                }
516            }
517
518            if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) {
519                break;
520            }
521
522            ifStatement = ifStatement.elseStatement;
523        }
524
525        return keywords;
526    }
527
528    /**
529     * Whether or not a 'node' is preceded by a label of the given string.
530     * Note: 'node' cannot be a SourceFile.
531     */
532    function isLabeledBy(node: Node, labelName: __String): boolean {
533        return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName);
534    }
535}
536