Support find-all-references for default exports
This commit is contained in:
parent
77a504bb64
commit
0ca4cb25d6
3 changed files with 82 additions and 51 deletions
|
@ -395,7 +395,7 @@ namespace ts {
|
|||
|
||||
function isShorthandAmbientModule(node: Node): boolean {
|
||||
// The only kind of module that can be missing a body is a shorthand ambient module.
|
||||
return node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
|
||||
return node && node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
|
||||
}
|
||||
|
||||
export function isBlockScopedContainerTopLevel(node: Node): boolean {
|
||||
|
@ -3104,7 +3104,11 @@ namespace ts {
|
|||
}
|
||||
|
||||
export function getLocalSymbolForExportDefault(symbol: Symbol) {
|
||||
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
|
||||
return isExportDefaultSymbol(symbol) ? symbol.valueDeclaration.localSymbol : undefined;
|
||||
}
|
||||
|
||||
export function isExportDefaultSymbol(symbol: Symbol): boolean {
|
||||
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default);
|
||||
}
|
||||
|
||||
/** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */
|
||||
|
|
|
@ -15,14 +15,13 @@ namespace ts.FindAllReferences {
|
|||
|
||||
// `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword,
|
||||
// so we have to specify that we want the constructor symbol.
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
if (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) {
|
||||
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles, typeChecker, cancellationToken);
|
||||
}
|
||||
let symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
// Could not find a symbol e.g. unknown identifier
|
||||
if (!symbol) {
|
||||
if (!implementations && node.kind === SyntaxKind.StringLiteral) {
|
||||
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles, typeChecker, cancellationToken);
|
||||
}
|
||||
// Can't have references to something that we have no symbol for.
|
||||
return undefined;
|
||||
}
|
||||
|
@ -34,9 +33,27 @@ namespace ts.FindAllReferences {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const aliasedSymbol = followAliasIfNecessary(symbol, node, typeChecker);
|
||||
const isShorthandModule = ts.isShorthandAmbientModuleSymbol(aliasedSymbol);
|
||||
// Don't follow alias for shorthand modules because we lose information that way.
|
||||
if (!isShorthandModule) {
|
||||
symbol = aliasedSymbol;
|
||||
}
|
||||
|
||||
// Compute the meaning from the location and the symbol it references
|
||||
const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations);
|
||||
|
||||
const result: ReferencedSymbol[] = [];
|
||||
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
|
||||
const symbolToIndex: number[] = [];
|
||||
const inheritsFromCache: Map<boolean> = createMap<boolean>();
|
||||
|
||||
// Build the set of symbols to search for, initially it has only the current symbol
|
||||
const searchSymbols = populateSearchSymbolSet(symbol, node, typeChecker, implementations, isShorthandModule ? aliasedSymbol : undefined);
|
||||
function isSearchedFor(symbol: Symbol): boolean {
|
||||
return contains(searchSymbols, symbol);
|
||||
}
|
||||
|
||||
// Get the text to search for.
|
||||
// Note: if this is an external module symbol, the name doesn't include quotes.
|
||||
const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
|
||||
|
@ -44,25 +61,28 @@ namespace ts.FindAllReferences {
|
|||
// Try to get the smallest valid scope that we can limit our search to;
|
||||
// otherwise we'll need to search globally (i.e. include each file).
|
||||
const scope = getSymbolScope(symbol);
|
||||
|
||||
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
|
||||
const symbolToIndex: number[] = [];
|
||||
|
||||
const result: ReferencedSymbol[] = [];
|
||||
if (scope) {
|
||||
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken);
|
||||
getRefs(scope, declaredName);
|
||||
}
|
||||
else {
|
||||
const internedName = getInternedName(symbol, node);
|
||||
const isDefault = isExportDefaultSymbol(symbol);
|
||||
const internedName = isDefault ? symbol.valueDeclaration.localSymbol.name : getInternedName(symbol, node);
|
||||
for (const sourceFile of sourceFiles) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
if (sourceFileHasName(sourceFile, internedName)) {
|
||||
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken);
|
||||
const searchName = (isDefault ? getDefaultImportName(symbol, sourceFile, typeChecker) : undefined) ||
|
||||
(sourceFileHasName(sourceFile, internedName) ? declaredName : undefined);
|
||||
if (searchName !== undefined) {
|
||||
getRefs(sourceFile, searchName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function getRefs(scope: ts.Node, searchName: string): void {
|
||||
getReferencesInNode(scope, symbol, searchName, node, searchMeaning, findInStrings, findInComments, result,
|
||||
symbolToIndex, implementations, typeChecker, cancellationToken, isSearchedFor, inheritsFromCache);
|
||||
}
|
||||
}
|
||||
|
||||
/** getReferencedSymbols for special node kinds. */
|
||||
|
@ -100,6 +120,23 @@ namespace ts.FindAllReferences {
|
|||
return getNameTable(sourceFile).get(name) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a symbol, see if any of the imports in a source file reference it.
|
||||
* Only call this if `symbol` is a default export.
|
||||
*/
|
||||
function getDefaultImportName(symbol: Symbol, sourceFile: SourceFile, checker: ts.TypeChecker): string | undefined {
|
||||
for (const importSpecifier of sourceFile.imports) {
|
||||
const importDecl = importSpecifier.parent as ts.ImportDeclaration;
|
||||
Debug.assert(importDecl.moduleSpecifier === importSpecifier);
|
||||
const defaultName = importDecl.importClause.name;
|
||||
const defaultReferencedSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(defaultName));
|
||||
if (symbol === defaultReferencedSymbol) {
|
||||
return defaultName.text;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDefinition(symbol: Symbol, node: Node, typeChecker: TypeChecker): ReferencedSymbolDefinitionInfo {
|
||||
const { displayParts, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, node.getSourceFile(), getContainerNode(node), node);
|
||||
const name = displayParts.map(p => p.text).join("");
|
||||
|
@ -177,11 +214,6 @@ namespace ts.FindAllReferences {
|
|||
return location.text;
|
||||
}
|
||||
|
||||
// Try to get the local symbol if we're dealing with an 'export default'
|
||||
// since that symbol has the "true" name.
|
||||
const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol);
|
||||
symbol = localExportDefaultSymbol || symbol;
|
||||
|
||||
return stripQuotes(symbol.name);
|
||||
}
|
||||
|
||||
|
@ -398,7 +430,9 @@ namespace ts.FindAllReferences {
|
|||
symbolToIndex: number[],
|
||||
implementations: boolean,
|
||||
typeChecker: TypeChecker,
|
||||
cancellationToken: CancellationToken): void {
|
||||
cancellationToken: CancellationToken,
|
||||
isSearchedFor: (symbol: Symbol) => boolean,
|
||||
inheritsFromCache: Map<boolean>): void {
|
||||
|
||||
const sourceFile = container.getSourceFile();
|
||||
|
||||
|
@ -406,12 +440,6 @@ namespace ts.FindAllReferences {
|
|||
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken);
|
||||
|
||||
const parents = getParentSymbolsOfPropertyAccess();
|
||||
const inheritsFromCache: Map<boolean> = createMap<boolean>();
|
||||
// Build the set of symbols to search for, initially it has only the current symbol
|
||||
const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation, typeChecker, implementations);
|
||||
function isSearchedFor(symbol: Symbol): boolean {
|
||||
return contains(searchSymbols, symbol);
|
||||
}
|
||||
|
||||
for (const position of possiblePositions) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
@ -456,11 +484,11 @@ namespace ts.FindAllReferences {
|
|||
addReferenceToRelatedSymbol(referenceLocation, relatedSymbol);
|
||||
}
|
||||
/* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment
|
||||
* has two meaning : property name and property value. Therefore when we do findAllReference at the position where
|
||||
* an identifier is declared, the language service should return the position of the variable declaration as well as
|
||||
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
|
||||
* position of property accessing, the referenceEntry of such position will be handled in the first case.
|
||||
*/
|
||||
* has two meanings: property name and property value. Therefore when we do findAllReference at the position where
|
||||
* an identifier is declared, the language service should return the position of the variable declaration as well as
|
||||
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
|
||||
* position of property accessing, the referenceEntry of such position will be handled in the first case.
|
||||
*/
|
||||
else if (!(referenceSymbol.flags & SymbolFlags.Transient) && isSearchedFor(shorthandValueSymbol)) {
|
||||
addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol);
|
||||
}
|
||||
|
@ -472,10 +500,10 @@ namespace ts.FindAllReferences {
|
|||
return;
|
||||
|
||||
/* If we are just looking for implementations and this is a property access expression, we need to get the
|
||||
* symbol of the local type of the symbol the property is being accessed on. This is because our search
|
||||
* symbol may have a different parent symbol if the local type's symbol does not declare the property
|
||||
* being accessed (i.e. it is declared in some parent class or interface)
|
||||
*/
|
||||
* symbol of the local type of the symbol the property is being accessed on. This is because our search
|
||||
* symbol may have a different parent symbol if the local type's symbol does not declare the property
|
||||
* being accessed (i.e. it is declared in some parent class or interface)
|
||||
*/
|
||||
function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined {
|
||||
if (implementations) {
|
||||
const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation);
|
||||
|
@ -994,7 +1022,7 @@ namespace ts.FindAllReferences {
|
|||
}
|
||||
}
|
||||
|
||||
function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean): Symbol[] {
|
||||
function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean, aliasSymbol?: Symbol): Symbol[] {
|
||||
// The search set contains at least the current symbol
|
||||
let result = [symbol];
|
||||
|
||||
|
@ -1009,18 +1037,6 @@ namespace ts.FindAllReferences {
|
|||
}
|
||||
}
|
||||
|
||||
// If the symbol is an alias, add what it aliases to the list
|
||||
// import {a} from "mod";
|
||||
// export {a}
|
||||
// If the symbol is an alias to default declaration, add what it aliases to the list
|
||||
// declare "mod" { export default class B { } }
|
||||
// import B from "mod";
|
||||
//// For export specifiers, the exported name can be referring to a local symbol, e.g.:
|
||||
//// import {a} from "mod";
|
||||
//// export {a as somethingElse}
|
||||
//// We want the *local* declaration of 'a' as declared in the import,
|
||||
//// *not* as declared within "mod" (or farther)
|
||||
const aliasSymbol = getAliasSymbolForPropertyNameSymbol(symbol, location, typeChecker);
|
||||
if (aliasSymbol) {
|
||||
result = result.concat(populateSearchSymbolSet(aliasSymbol, location, typeChecker, implementations));
|
||||
}
|
||||
|
@ -1225,7 +1241,7 @@ namespace ts.FindAllReferences {
|
|||
}
|
||||
|
||||
/** Gets all symbols for one property. Does not get symbols for every property. */
|
||||
function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] {
|
||||
function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] | undefined {
|
||||
const objectLiteral = <ObjectLiteralExpression>node.parent;
|
||||
const contextualType = typeChecker.getContextualType(objectLiteral);
|
||||
const name = getNameFromObjectLiteralElement(node);
|
||||
|
|
11
tests/cases/fourslash/findAllRefsForDefaultExport.ts
Normal file
11
tests/cases/fourslash/findAllRefsForDefaultExport.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a.ts
|
||||
////export default function /*def*/[|f|]() {}
|
||||
|
||||
// @Filename: b.ts
|
||||
////import [|g|] from "./a";
|
||||
/////*ref*/[|g|]();
|
||||
|
||||
verify.rangesReferenceEachOther();
|
||||
verify.goToDefinition("ref", "def");
|
Loading…
Reference in a new issue