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