From 8dc66e4665e6aa7c3c822e066bb1c59e72fa7591 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 14 Sep 2017 12:38:17 -0700 Subject: [PATCH] Cleanup navTo (#18150) --- src/services/navigateTo.ts | 286 ++++++++++++++++----------------- src/services/patternMatcher.ts | 4 +- 2 files changed, 144 insertions(+), 146 deletions(-) diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index ec7b011456f..7fa177b6a30 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -21,185 +21,183 @@ namespace ts.NavigateTo { } forEachEntry(sourceFile.getNamedDeclarations(), (declarations, 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) { - return; // continue to next named declarations - } - - 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 true; // Break out of named declarations and go to the next source file. - } - - matches = patternMatcher.getMatches(containers, name); - - if (!matches) { - return; // continue to next named declarations - } - } - - const fileName = sourceFile.fileName; - const matchKind = bestMatchKind(matches); - rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration }); - } - } + getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems); }); } - // 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 as NamedDeclaration).name); - const imported = checker.getAliasedSymbol(importer); - return importer.escapedName !== imported.escapedName; - } - else { - return true; - } - }); - rawItems.sort(compareNavigateToItems); if (maxResultCount !== undefined) { rawItems = rawItems.slice(0, maxResultCount); } + return rawItems.map(createNavigateToItem); + } - const items = map(rawItems, createNavigateToItem); + function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: ReadonlyArray, checker: TypeChecker, fileName: string, rawItems: Push): 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 matches = patternMatcher.getMatchesForLastSegmentOfPattern(name); - return items; + if (!matches) { + return; // continue to next named declarations + } - function allMatchesAreCaseSensitive(matches: PatternMatch[]): boolean { - Debug.assert(matches.length > 0); + for (const declaration of declarations) { + if (!shouldKeepItem(declaration, checker)) { + continue; + } - // This is a case sensitive match, only if all the submatches were case sensitive. - for (const match of matches) { - if (!match.isCaseSensitive) { + // It was a match! If the pattern has dots in it, then also see if the + // declaration container matches as well. + let containerMatches = matches; + if (patternMatcher.patternContainsDots) { + containerMatches = patternMatcher.getMatches(getContainers(declaration), name); + if (!containerMatches) { + continue; + } + } + + const matchKind = bestMatchKind(containerMatches); + const isCaseSensitive = allMatchesAreCaseSensitive(containerMatches); + rawItems.push({ name, fileName, matchKind, isCaseSensitive, declaration }); + } + } + + function shouldKeepItem(declaration: Declaration, checker: ts.TypeChecker): boolean { + switch (declaration.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name); + const imported = checker.getAliasedSymbol(importer); + return importer.escapedName !== imported.escapedName; + default: + return true; + } + } + + function allMatchesAreCaseSensitive(matches: ReadonlyArray): 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 tryAddSingleDeclarationName(declaration: Declaration, containers: string[]): boolean { + if (declaration) { + const name = getNameOfDeclaration(declaration); + if (name) { + const text = getTextOfIdentifierOrLiteral(name as (Identifier | LiteralExpression)); + if (text !== undefined) { + containers.unshift(text); + } + else if (name.kind === SyntaxKind.ComputedPropertyName) { + return tryAddComputedPropertyName((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 as LiteralExpression); + if (text !== undefined) { + if (includeLastPortion) { + containers.unshift(text); + } return true; } - function tryAddSingleDeclarationName(declaration: Declaration, containers: string[]) { - if (declaration) { - const name = getNameOfDeclaration(declaration); - if (name) { - const text = getTextOfIdentifierOrLiteral(name as (Identifier | LiteralExpression)); - if (text !== undefined) { - containers.unshift(text); - } - else if (name.kind === SyntaxKind.ComputedPropertyName) { - return tryAddComputedPropertyName((name).expression, containers, /*includeLastPortion*/ true); - } - else { - // Don't know how to add this. - return false; - } - } + if (expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = expression; + if (includeLastPortion) { + containers.unshift(propertyAccess.name.text); } - return true; + return tryAddComputedPropertyName(propertyAccess.expression, containers, /*includeLastPortion*/ 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 as LiteralExpression); - if (text !== undefined) { - if (includeLastPortion) { - containers.unshift(text); - } - return true; + return false; + } + + function getContainers(declaration: Declaration): 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.kind === SyntaxKind.ComputedPropertyName) { + if (!tryAddComputedPropertyName((name).expression, containers, /*includeLastPortion*/ false)) { + return undefined; } - - if (expression.kind === SyntaxKind.PropertyAccessExpression) { - const propertyAccess = expression; - if (includeLastPortion) { - containers.unshift(propertyAccess.name.text); - } - - return tryAddComputedPropertyName(propertyAccess.expression, containers, /*includeLastPortion*/ true); - } - - return false; } - function getContainers(declaration: Declaration) { - const containers: string[] = []; + // Now, walk up our containers, adding all their names to the container array. + declaration = getContainerNode(declaration); - // 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.kind === SyntaxKind.ComputedPropertyName) { - if (!tryAddComputedPropertyName((name).expression, containers, /*includeLastPortion*/ false)) { - return undefined; - } + while (declaration) { + if (!tryAddSingleDeclarationName(declaration, containers)) { + 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; - } + return containers; + } - declaration = getContainerNode(declaration); + function bestMatchKind(matches: ReadonlyArray): PatternMatchKind { + Debug.assert(matches.length > 0); + let bestMatchKind = PatternMatchKind.camelCase; + + for (const match of matches) { + const kind = match.kind; + if (kind < bestMatchKind) { + bestMatchKind = kind; } - - return containers; } - function bestMatchKind(matches: PatternMatch[]) { - Debug.assert(matches.length > 0); - let bestMatchKind = PatternMatchKind.camelCase; + return bestMatchKind; + } - for (const match of matches) { - const kind = match.kind; - if (kind < bestMatchKind) { - bestMatchKind = kind; - } - } + function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem): number { + // 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 || + ts.compareStringsCaseInsensitive(i1.name, i2.name) || + ts.compareStrings(i1.name, i2.name); + } - 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 || - ts.compareStringsCaseInsensitive(i1.name, i2.name) || - ts.compareStrings(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], - 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 ? (containerName).text : "", - containerKind: containerName ? getNodeKind(container) : ScriptElementKind.unknown - }; - } + 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], + 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 ? (containerName).text : "", + containerKind: containerName ? getNodeKind(container) : ScriptElementKind.unknown + }; } } diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 396e53810ce..04f9d906d35 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -47,7 +47,7 @@ namespace ts { // Fully checks a candidate, with an dotted container, against the search pattern. // The candidate must match the last part of the search pattern, and the dotted container // must match the preceding segments of the pattern. - getMatches(candidateContainers: string[], candidate: string): PatternMatch[]; + getMatches(candidateContainers: string[], candidate: string): PatternMatch[] | undefined; // Whether or not the pattern contained dots or not. Clients can use this to determine // If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient. @@ -139,7 +139,7 @@ namespace ts { return matchSegment(candidate, lastOrUndefined(dotSeparatedSegments)); } - function getMatches(candidateContainers: string[], candidate: string): PatternMatch[] { + function getMatches(candidateContainers: string[], candidate: string): PatternMatch[] | undefined { if (skipMatch(candidate)) { return undefined; }