• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    CancellationToken, compareStringsCaseSensitiveUI, compareValues, createPatternMatcher, createTextSpanFromNode,
3    Declaration, emptyArray, Expression, getContainerNode, getNameOfDeclaration, getNodeKind, getNodeModifiers,
4    getTextOfIdentifierOrLiteral, Identifier, ImportClause, ImportEqualsDeclaration, ImportSpecifier,
5    isPropertyAccessExpression, isPropertyNameLiteral, NavigateToItem, Node, PatternMatcher, PatternMatchKind, Push,
6    ScriptElementKind, SourceFile, SyntaxKind, TypeChecker,
7} from "./_namespaces/ts";
8
9interface RawNavigateToItem {
10    readonly name: string;
11    readonly fileName: string;
12    readonly matchKind: PatternMatchKind;
13    readonly isCaseSensitive: boolean;
14    readonly declaration: Declaration;
15}
16
17/** @internal */
18export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] {
19    const patternMatcher = createPatternMatcher(searchValue);
20    if (!patternMatcher) return emptyArray;
21    const rawItems: RawNavigateToItem[] = [];
22
23    // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
24    for (const sourceFile of sourceFiles) {
25        cancellationToken.throwIfCancellationRequested();
26
27        if (excludeDtsFiles && sourceFile.isDeclarationFile) {
28            continue;
29        }
30
31        sourceFile.getNamedDeclarations().forEach((declarations, name) => {
32            getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems);
33        });
34    }
35
36    rawItems.sort(compareNavigateToItems);
37    return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem);
38}
39
40function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push<RawNavigateToItem>): void {
41    // First do a quick check to see if the name of the declaration matches the
42    // last portion of the (possibly) dotted name they're searching for.
43    const match = patternMatcher.getMatchForLastSegmentOfPattern(name);
44    if (!match) {
45        return; // continue to next named declarations
46    }
47
48    for (const declaration of declarations) {
49        if (!shouldKeepItem(declaration, checker)) continue;
50
51        if (patternMatcher.patternContainsDots) {
52            // If the pattern has dots in it, then also see if the declaration container matches as well.
53            const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name);
54            if (fullMatch) {
55                rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration });
56            }
57        }
58        else {
59            rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration });
60        }
61    }
62}
63
64function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean {
65    switch (declaration.kind) {
66        case SyntaxKind.ImportClause:
67        case SyntaxKind.ImportSpecifier:
68        case SyntaxKind.ImportEqualsDeclaration:
69            const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217
70            const imported = checker.getAliasedSymbol(importer);
71            return importer.escapedName !== imported.escapedName;
72        default:
73            return true;
74    }
75}
76
77function tryAddSingleDeclarationName(declaration: Declaration, containers: Push<string>): boolean {
78    const name = getNameOfDeclaration(declaration);
79    return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers));
80}
81
82// Only added the names of computed properties if they're simple dotted expressions, like:
83//
84//      [X.Y.Z]() { }
85function tryAddComputedPropertyName(expression: Expression, containers: Push<string>): boolean {
86    return pushLiteral(expression, containers)
87        || isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers);
88}
89
90function pushLiteral(node: Node, containers: Push<string>): boolean {
91    return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true);
92}
93
94function getContainers(declaration: Declaration): readonly string[] {
95    const containers: string[] = [];
96
97    // First, if we started with a computed property name, then add all but the last
98    // portion into the container array.
99    const name = getNameOfDeclaration(declaration);
100    if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) {
101        return emptyArray;
102    }
103    // Don't include the last portion.
104    containers.shift();
105
106    // Now, walk up our containers, adding all their names to the container array.
107    let container = getContainerNode(declaration);
108
109    while (container) {
110        if (!tryAddSingleDeclarationName(container, containers)) {
111            return emptyArray;
112        }
113
114        container = getContainerNode(container);
115    }
116
117    return containers.reverse();
118}
119
120function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) {
121    // TODO(cyrusn): get the gamut of comparisons that VS already uses here.
122    return compareValues(i1.matchKind, i2.matchKind)
123        || compareStringsCaseSensitiveUI(i1.name, i2.name);
124}
125
126function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem {
127    const declaration = rawItem.declaration;
128    const container = getContainerNode(declaration);
129    const containerName = container && getNameOfDeclaration(container);
130    return {
131        name: rawItem.name,
132        kind: getNodeKind(declaration),
133        kindModifiers: getNodeModifiers(declaration),
134        matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind,
135        isCaseSensitive: rawItem.isCaseSensitive,
136        fileName: rawItem.fileName,
137        textSpan: createTextSpanFromNode(declaration),
138        // TODO(jfreeman): What should be the containerName when the container has a computed name?
139        containerName: containerName ? (containerName as Identifier).text : "",
140        containerKind: containerName ? getNodeKind(container) : ScriptElementKind.unknown,
141    };
142}
143