TypeScript/src/services/navigateTo.ts

137 lines
6.5 KiB
TypeScript

/* @internal */
namespace ts.NavigateTo {
interface RawNavigateToItem {
readonly name: string;
readonly fileName: string;
readonly matchKind: PatternMatchKind;
readonly isCaseSensitive: boolean;
readonly declaration: Declaration;
}
export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] {
const patternMatcher = createPatternMatcher(searchValue);
if (!patternMatcher) return emptyArray;
const rawItems: RawNavigateToItem[] = [];
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
for (const sourceFile of sourceFiles) {
cancellationToken.throwIfCancellationRequested();
if (excludeDtsFiles && sourceFile.isDeclarationFile) {
continue;
}
sourceFile.getNamedDeclarations().forEach((declarations, name) => {
getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems);
});
}
rawItems.sort(compareNavigateToItems);
return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem);
}
function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push<RawNavigateToItem>): void {
// First do a quick check to see if the name of the declaration matches the
// last portion of the (possibly) dotted name they're searching for.
const match = patternMatcher.getMatchForLastSegmentOfPattern(name);
if (!match) {
return; // continue to next named declarations
}
for (const declaration of declarations) {
if (!shouldKeepItem(declaration, checker)) continue;
if (patternMatcher.patternContainsDots) {
// If the pattern has dots in it, then also see if the declaration container matches as well.
const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name);
if (fullMatch) {
rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration });
}
}
else {
rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration });
}
}
}
function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean {
switch (declaration.kind) {
case SyntaxKind.ImportClause:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportEqualsDeclaration:
const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217
const imported = checker.getAliasedSymbol(importer);
return importer.escapedName !== imported.escapedName;
default:
return true;
}
}
function tryAddSingleDeclarationName(declaration: Declaration, containers: Push<string>): boolean {
const name = getNameOfDeclaration(declaration);
return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers));
}
// Only added the names of computed properties if they're simple dotted expressions, like:
//
// [X.Y.Z]() { }
function tryAddComputedPropertyName(expression: Expression, containers: Push<string>): boolean {
return pushLiteral(expression, containers)
|| isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers);
}
function pushLiteral(node: Node, containers: Push<string>): boolean {
return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true);
}
function getContainers(declaration: Declaration): readonly string[] {
const containers: string[] = [];
// First, if we started with a computed property name, then add all but the last
// portion into the container array.
const name = getNameOfDeclaration(declaration);
if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) {
return emptyArray;
}
// Don't include the last portion.
containers.shift();
// Now, walk up our containers, adding all their names to the container array.
let container = getContainerNode(declaration);
while (container) {
if (!tryAddSingleDeclarationName(container, containers)) {
return emptyArray;
}
container = getContainerNode(container);
}
return containers.reverse();
}
function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) {
// TODO(cyrusn): get the gamut of comparisons that VS already uses here.
return compareValues(i1.matchKind, i2.matchKind)
|| compareStringsCaseSensitiveUI(i1.name, i2.name);
}
function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem {
const declaration = rawItem.declaration;
const container = getContainerNode(declaration);
const containerName = container && getNameOfDeclaration(container);
return {
name: rawItem.name,
kind: getNodeKind(declaration),
kindModifiers: getNodeModifiers(declaration),
matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind,
isCaseSensitive: rawItem.isCaseSensitive,
fileName: rawItem.fileName,
textSpan: createTextSpanFromNode(declaration),
// TODO(jfreeman): What should be the containerName when the container has a computed name?
containerName: containerName ? (<Identifier>containerName).text : "",
containerKind: containerName ? getNodeKind(container!) : ScriptElementKind.unknown, // TODO: GH#18217 Just use `container ? ...`
};
}
}