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