mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 03:23:08 -06:00
208 lines
8.9 KiB
TypeScript
208 lines
8.9 KiB
TypeScript
/* @internal */
|
|
namespace ts.NavigateTo {
|
|
type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration };
|
|
|
|
export function getNavigateToItems(program: Program, checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number): NavigateToItem[] {
|
|
const patternMatcher = createPatternMatcher(searchValue);
|
|
let rawItems: RawNavigateToItem[] = [];
|
|
|
|
// This means "compare in a case insensitive manner."
|
|
const baseSensitivity: Intl.CollatorOptions = { sensitivity: "base" };
|
|
|
|
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
|
|
forEach(program.getSourceFiles(), sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
const nameToDeclarations = sourceFile.getNamedDeclarations();
|
|
for (const name in nameToDeclarations) {
|
|
const declarations = getProperty(nameToDeclarations, name);
|
|
if (declarations) {
|
|
// 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.
|
|
let matches = patternMatcher.getMatchesForLastSegmentOfPattern(name);
|
|
|
|
if (!matches) {
|
|
continue;
|
|
}
|
|
|
|
for (const declaration of declarations) {
|
|
// It was a match! If the pattern has dots in it, then also see if the
|
|
// declaration container matches as well.
|
|
if (patternMatcher.patternContainsDots) {
|
|
const containers = getContainers(declaration);
|
|
if (!containers) {
|
|
return undefined;
|
|
}
|
|
|
|
matches = patternMatcher.getMatches(containers, name);
|
|
|
|
if (!matches) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const fileName = sourceFile.fileName;
|
|
const matchKind = bestMatchKind(matches);
|
|
rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration });
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Remove imports when the imported declaration is already in the list and has the same name.
|
|
rawItems = filter(rawItems, item => {
|
|
const decl = item.declaration;
|
|
if (decl.kind === SyntaxKind.ImportClause || decl.kind === SyntaxKind.ImportSpecifier || decl.kind === SyntaxKind.ImportEqualsDeclaration) {
|
|
const importer = checker.getSymbolAtLocation(decl.name);
|
|
const imported = checker.getAliasedSymbol(importer);
|
|
return importer.name !== imported.name;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
rawItems.sort(compareNavigateToItems);
|
|
if (maxResultCount !== undefined) {
|
|
rawItems = rawItems.slice(0, maxResultCount);
|
|
}
|
|
|
|
const items = map(rawItems, createNavigateToItem);
|
|
|
|
return items;
|
|
|
|
function allMatchesAreCaseSensitive(matches: PatternMatch[]): boolean {
|
|
Debug.assert(matches.length > 0);
|
|
|
|
// This is a case sensitive match, only if all the submatches were case sensitive.
|
|
for (const match of matches) {
|
|
if (!match.isCaseSensitive) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getTextOfIdentifierOrLiteral(node: Node) {
|
|
if (node) {
|
|
if (node.kind === SyntaxKind.Identifier ||
|
|
node.kind === SyntaxKind.StringLiteral ||
|
|
node.kind === SyntaxKind.NumericLiteral) {
|
|
|
|
return (<Identifier | LiteralExpression>node).text;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function tryAddSingleDeclarationName(declaration: Declaration, containers: string[]) {
|
|
if (declaration && declaration.name) {
|
|
const text = getTextOfIdentifierOrLiteral(declaration.name);
|
|
if (text !== undefined) {
|
|
containers.unshift(text);
|
|
}
|
|
else if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
return tryAddComputedPropertyName((<ComputedPropertyName>declaration.name).expression, containers, /*includeLastPortion*/ true);
|
|
}
|
|
else {
|
|
// Don't know how to add this.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Only added the names of computed properties if they're simple dotted expressions, like:
|
|
//
|
|
// [X.Y.Z]() { }
|
|
function tryAddComputedPropertyName(expression: Expression, containers: string[], includeLastPortion: boolean): boolean {
|
|
const text = getTextOfIdentifierOrLiteral(expression);
|
|
if (text !== undefined) {
|
|
if (includeLastPortion) {
|
|
containers.unshift(text);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (expression.kind === SyntaxKind.PropertyAccessExpression) {
|
|
const propertyAccess = <PropertyAccessExpression>expression;
|
|
if (includeLastPortion) {
|
|
containers.unshift(propertyAccess.name.text);
|
|
}
|
|
|
|
return tryAddComputedPropertyName(propertyAccess.expression, containers, /*includeLastPortion*/ true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getContainers(declaration: Declaration) {
|
|
const containers: string[] = [];
|
|
|
|
// First, if we started with a computed property name, then add all but the last
|
|
// portion into the container array.
|
|
if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
if (!tryAddComputedPropertyName((<ComputedPropertyName>declaration.name).expression, containers, /*includeLastPortion*/ false)) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// Now, walk up our containers, adding all their names to the container array.
|
|
declaration = getContainerNode(declaration);
|
|
|
|
while (declaration) {
|
|
if (!tryAddSingleDeclarationName(declaration, containers)) {
|
|
return undefined;
|
|
}
|
|
|
|
declaration = getContainerNode(declaration);
|
|
}
|
|
|
|
return containers;
|
|
}
|
|
|
|
function bestMatchKind(matches: PatternMatch[]) {
|
|
Debug.assert(matches.length > 0);
|
|
let bestMatchKind = PatternMatchKind.camelCase;
|
|
|
|
for (const match of matches) {
|
|
const kind = match.kind;
|
|
if (kind < bestMatchKind) {
|
|
bestMatchKind = kind;
|
|
}
|
|
}
|
|
|
|
return bestMatchKind;
|
|
}
|
|
|
|
function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) {
|
|
// TODO(cyrusn): get the gamut of comparisons that VS already uses here.
|
|
// Right now we just sort by kind first, and then by name of the item.
|
|
// We first sort case insensitively. So "Aaa" will come before "bar".
|
|
// Then we sort case sensitively, so "aaa" will come before "Aaa".
|
|
return i1.matchKind - i2.matchKind ||
|
|
i1.name.localeCompare(i2.name, undefined, baseSensitivity) ||
|
|
i1.name.localeCompare(i2.name);
|
|
}
|
|
|
|
function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem {
|
|
const declaration = rawItem.declaration;
|
|
const container = <Declaration>getContainerNode(declaration);
|
|
return {
|
|
name: rawItem.name,
|
|
kind: getNodeKind(declaration),
|
|
kindModifiers: getNodeModifiers(declaration),
|
|
matchKind: PatternMatchKind[rawItem.matchKind],
|
|
isCaseSensitive: rawItem.isCaseSensitive,
|
|
fileName: rawItem.fileName,
|
|
textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()),
|
|
// TODO(jfreeman): What should be the containerName when the container has a computed name?
|
|
containerName: container && container.name ? (<Identifier>container.name).text : "",
|
|
containerKind: container && container.name ? getNodeKind(container) : ""
|
|
};
|
|
}
|
|
}
|
|
} |