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