Skip to content

Commit 0ca4cb2

Browse files
author
Andy Hanson
committed
Support find-all-references for default exports
1 parent 77a504b commit 0ca4cb2

File tree

3 files changed

+82
-51
lines changed

3 files changed

+82
-51
lines changed

src/compiler/utilities.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ namespace ts {
395395

396396
function isShorthandAmbientModule(node: Node): boolean {
397397
// The only kind of module that can be missing a body is a shorthand ambient module.
398-
return node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
398+
return node && node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
399399
}
400400

401401
export function isBlockScopedContainerTopLevel(node: Node): boolean {
@@ -3104,7 +3104,11 @@ namespace ts {
31043104
}
31053105

31063106
export function getLocalSymbolForExportDefault(symbol: Symbol) {
3107-
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
3107+
return isExportDefaultSymbol(symbol) ? symbol.valueDeclaration.localSymbol : undefined;
3108+
}
3109+
3110+
export function isExportDefaultSymbol(symbol: Symbol): boolean {
3111+
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default);
31083112
}
31093113

31103114
/** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */

src/services/findAllReferences.ts

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ namespace ts.FindAllReferences {
1515

1616
// `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword,
1717
// so we have to specify that we want the constructor symbol.
18-
const symbol = typeChecker.getSymbolAtLocation(node);
19-
20-
if (!implementations && !symbol && node.kind === SyntaxKind.StringLiteral) {
21-
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles, typeChecker, cancellationToken);
22-
}
18+
let symbol = typeChecker.getSymbolAtLocation(node);
2319

2420
// Could not find a symbol e.g. unknown identifier
2521
if (!symbol) {
22+
if (!implementations && node.kind === SyntaxKind.StringLiteral) {
23+
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles, typeChecker, cancellationToken);
24+
}
2625
// Can't have references to something that we have no symbol for.
2726
return undefined;
2827
}
@@ -34,35 +33,56 @@ namespace ts.FindAllReferences {
3433
return undefined;
3534
}
3635

36+
const aliasedSymbol = followAliasIfNecessary(symbol, node, typeChecker);
37+
const isShorthandModule = ts.isShorthandAmbientModuleSymbol(aliasedSymbol);
38+
// Don't follow alias for shorthand modules because we lose information that way.
39+
if (!isShorthandModule) {
40+
symbol = aliasedSymbol;
41+
}
42+
3743
// Compute the meaning from the location and the symbol it references
3844
const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations);
3945

46+
const result: ReferencedSymbol[] = [];
47+
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
48+
const symbolToIndex: number[] = [];
49+
const inheritsFromCache: Map<boolean> = createMap<boolean>();
50+
51+
// Build the set of symbols to search for, initially it has only the current symbol
52+
const searchSymbols = populateSearchSymbolSet(symbol, node, typeChecker, implementations, isShorthandModule ? aliasedSymbol : undefined);
53+
function isSearchedFor(symbol: Symbol): boolean {
54+
return contains(searchSymbols, symbol);
55+
}
56+
4057
// Get the text to search for.
4158
// Note: if this is an external module symbol, the name doesn't include quotes.
4259
const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
4360

4461
// Try to get the smallest valid scope that we can limit our search to;
4562
// otherwise we'll need to search globally (i.e. include each file).
4663
const scope = getSymbolScope(symbol);
47-
48-
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
49-
const symbolToIndex: number[] = [];
50-
51-
const result: ReferencedSymbol[] = [];
5264
if (scope) {
53-
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken);
65+
getRefs(scope, declaredName);
5466
}
5567
else {
56-
const internedName = getInternedName(symbol, node);
68+
const isDefault = isExportDefaultSymbol(symbol);
69+
const internedName = isDefault ? symbol.valueDeclaration.localSymbol.name : getInternedName(symbol, node);
5770
for (const sourceFile of sourceFiles) {
5871
cancellationToken.throwIfCancellationRequested();
59-
if (sourceFileHasName(sourceFile, internedName)) {
60-
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex, implementations, typeChecker, cancellationToken);
72+
const searchName = (isDefault ? getDefaultImportName(symbol, sourceFile, typeChecker) : undefined) ||
73+
(sourceFileHasName(sourceFile, internedName) ? declaredName : undefined);
74+
if (searchName !== undefined) {
75+
getRefs(sourceFile, searchName);
6176
}
6277
}
6378
}
6479

6580
return result;
81+
82+
function getRefs(scope: ts.Node, searchName: string): void {
83+
getReferencesInNode(scope, symbol, searchName, node, searchMeaning, findInStrings, findInComments, result,
84+
symbolToIndex, implementations, typeChecker, cancellationToken, isSearchedFor, inheritsFromCache);
85+
}
6686
}
6787

6888
/** getReferencedSymbols for special node kinds. */
@@ -100,6 +120,23 @@ namespace ts.FindAllReferences {
100120
return getNameTable(sourceFile).get(name) !== undefined;
101121
}
102122

123+
/**
124+
* Given a symbol, see if any of the imports in a source file reference it.
125+
* Only call this if `symbol` is a default export.
126+
*/
127+
function getDefaultImportName(symbol: Symbol, sourceFile: SourceFile, checker: ts.TypeChecker): string | undefined {
128+
for (const importSpecifier of sourceFile.imports) {
129+
const importDecl = importSpecifier.parent as ts.ImportDeclaration;
130+
Debug.assert(importDecl.moduleSpecifier === importSpecifier);
131+
const defaultName = importDecl.importClause.name;
132+
const defaultReferencedSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(defaultName));
133+
if (symbol === defaultReferencedSymbol) {
134+
return defaultName.text;
135+
}
136+
}
137+
return undefined;
138+
}
139+
103140
function getDefinition(symbol: Symbol, node: Node, typeChecker: TypeChecker): ReferencedSymbolDefinitionInfo {
104141
const { displayParts, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, node.getSourceFile(), getContainerNode(node), node);
105142
const name = displayParts.map(p => p.text).join("");
@@ -177,11 +214,6 @@ namespace ts.FindAllReferences {
177214
return location.text;
178215
}
179216

180-
// Try to get the local symbol if we're dealing with an 'export default'
181-
// since that symbol has the "true" name.
182-
const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol);
183-
symbol = localExportDefaultSymbol || symbol;
184-
185217
return stripQuotes(symbol.name);
186218
}
187219

@@ -398,20 +430,16 @@ namespace ts.FindAllReferences {
398430
symbolToIndex: number[],
399431
implementations: boolean,
400432
typeChecker: TypeChecker,
401-
cancellationToken: CancellationToken): void {
433+
cancellationToken: CancellationToken,
434+
isSearchedFor: (symbol: Symbol) => boolean,
435+
inheritsFromCache: Map<boolean>): void {
402436

403437
const sourceFile = container.getSourceFile();
404438

405439
const start = findInComments ? container.getFullStart() : container.getStart();
406440
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken);
407441

408442
const parents = getParentSymbolsOfPropertyAccess();
409-
const inheritsFromCache: Map<boolean> = createMap<boolean>();
410-
// Build the set of symbols to search for, initially it has only the current symbol
411-
const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation, typeChecker, implementations);
412-
function isSearchedFor(symbol: Symbol): boolean {
413-
return contains(searchSymbols, symbol);
414-
}
415443

416444
for (const position of possiblePositions) {
417445
cancellationToken.throwIfCancellationRequested();
@@ -456,11 +484,11 @@ namespace ts.FindAllReferences {
456484
addReferenceToRelatedSymbol(referenceLocation, relatedSymbol);
457485
}
458486
/* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment
459-
* has two meaning : property name and property value. Therefore when we do findAllReference at the position where
460-
* an identifier is declared, the language service should return the position of the variable declaration as well as
461-
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
462-
* position of property accessing, the referenceEntry of such position will be handled in the first case.
463-
*/
487+
* has two meanings: property name and property value. Therefore when we do findAllReference at the position where
488+
* an identifier is declared, the language service should return the position of the variable declaration as well as
489+
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
490+
* position of property accessing, the referenceEntry of such position will be handled in the first case.
491+
*/
464492
else if (!(referenceSymbol.flags & SymbolFlags.Transient) && isSearchedFor(shorthandValueSymbol)) {
465493
addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol);
466494
}
@@ -472,10 +500,10 @@ namespace ts.FindAllReferences {
472500
return;
473501

474502
/* If we are just looking for implementations and this is a property access expression, we need to get the
475-
* symbol of the local type of the symbol the property is being accessed on. This is because our search
476-
* symbol may have a different parent symbol if the local type's symbol does not declare the property
477-
* being accessed (i.e. it is declared in some parent class or interface)
478-
*/
503+
* symbol of the local type of the symbol the property is being accessed on. This is because our search
504+
* symbol may have a different parent symbol if the local type's symbol does not declare the property
505+
* being accessed (i.e. it is declared in some parent class or interface)
506+
*/
479507
function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined {
480508
if (implementations) {
481509
const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation);
@@ -994,7 +1022,7 @@ namespace ts.FindAllReferences {
9941022
}
9951023
}
9961024

997-
function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean): Symbol[] {
1025+
function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean, aliasSymbol?: Symbol): Symbol[] {
9981026
// The search set contains at least the current symbol
9991027
let result = [symbol];
10001028

@@ -1009,18 +1037,6 @@ namespace ts.FindAllReferences {
10091037
}
10101038
}
10111039

1012-
// If the symbol is an alias, add what it aliases to the list
1013-
// import {a} from "mod";
1014-
// export {a}
1015-
// If the symbol is an alias to default declaration, add what it aliases to the list
1016-
// declare "mod" { export default class B { } }
1017-
// import B from "mod";
1018-
//// For export specifiers, the exported name can be referring to a local symbol, e.g.:
1019-
//// import {a} from "mod";
1020-
//// export {a as somethingElse}
1021-
//// We want the *local* declaration of 'a' as declared in the import,
1022-
//// *not* as declared within "mod" (or farther)
1023-
const aliasSymbol = getAliasSymbolForPropertyNameSymbol(symbol, location, typeChecker);
10241040
if (aliasSymbol) {
10251041
result = result.concat(populateSearchSymbolSet(aliasSymbol, location, typeChecker, implementations));
10261042
}
@@ -1225,7 +1241,7 @@ namespace ts.FindAllReferences {
12251241
}
12261242

12271243
/** Gets all symbols for one property. Does not get symbols for every property. */
1228-
function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] {
1244+
function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] | undefined {
12291245
const objectLiteral = <ObjectLiteralExpression>node.parent;
12301246
const contextualType = typeChecker.getContextualType(objectLiteral);
12311247
const name = getNameFromObjectLiteralElement(node);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: a.ts
4+
////export default function /*def*/[|f|]() {}
5+
6+
// @Filename: b.ts
7+
////import [|g|] from "./a";
8+
/////*ref*/[|g|]();
9+
10+
verify.rangesReferenceEachOther();
11+
verify.goToDefinition("ref", "def");

0 commit comments

Comments
 (0)