Skip to content

Commit c7f473e

Browse files
author
Andy Hanson
committed
Better handle additional re-export cases
1 parent 4d0f912 commit c7f473e

File tree

5 files changed

+101
-46
lines changed

5 files changed

+101
-46
lines changed

src/services/findAllReferences.ts

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,16 @@ namespace ts.FindAllReferences {
235235
markSeenContainingTypeReference(containingTypeReference: Node): boolean;
236236

237237
/**
238-
* It's possible that we will encounter either side of `export { foo as bar } from "x";` more than once.
238+
* It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once.
239239
* For example:
240-
* export { foo as bar } from "a";
241-
* import { foo } from "a";
240+
* // b.ts
241+
* export { foo as bar } from "./a";
242+
* import { bar } from "./b";
242243
*
243244
* Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local).
244245
* But another reference to it may appear in the same source file.
245246
* See `tests/cases/fourslash/transitiveExportImports3.ts`.
246247
*/
247-
markSeenReExportLHS(lhs: Identifier): boolean;
248248
markSeenReExportRHS(rhs: Identifier): boolean;
249249
}
250250

@@ -259,7 +259,7 @@ namespace ts.FindAllReferences {
259259
return {
260260
...options,
261261
sourceFiles, isForConstructor, checker, cancellationToken, searchMeaning, inheritsFromCache, getImportSearches, createSearch, referenceAdder, addStringOrCommentReference,
262-
markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportLHS: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(),
262+
markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(),
263263
};
264264

265265
function getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult {
@@ -312,9 +312,7 @@ namespace ts.FindAllReferences {
312312
if (singleReferences.length) {
313313
const addRef = state.referenceAdder(exportSymbol, exportLocation);
314314
for (const singleRef of singleReferences) {
315-
if (state.markSeenReExportLHS(singleRef)) {
316-
addRef(singleRef);
317-
}
315+
addRef(singleRef);
318316
}
319317
}
320318

@@ -645,9 +643,15 @@ namespace ts.FindAllReferences {
645643
return;
646644
}
647645

648-
if (isExportSpecifier(referenceLocation.parent)) {
646+
const { parent } = referenceLocation;
647+
if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) {
648+
// This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again.
649+
return;
650+
}
651+
652+
if (isExportSpecifier(parent)) {
649653
Debug.assert(referenceLocation.kind === SyntaxKind.Identifier);
650-
getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, referenceLocation.parent, search, state);
654+
getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state);
651655
return;
652656
}
653657

@@ -669,39 +673,48 @@ namespace ts.FindAllReferences {
669673

670674
function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void {
671675
const { parent, propertyName, name } = exportSpecifier;
672-
searchForExport(getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker));
673-
674676
const exportDeclaration = parent.parent;
675-
if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
676-
searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
677+
const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker);
678+
if (!search.includes(localSymbol)) {
679+
return;
677680
}
678681

679-
function searchForExport(localSymbol: Symbol): void {
680-
if (!search.includes(localSymbol)) {
681-
return;
682+
if (!propertyName) {
683+
addRef()
684+
}
685+
else if (referenceLocation === propertyName) {
686+
// For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export.
687+
// For `export { foo as bar };`, where `foo` is a local, so add it now.
688+
if (!exportDeclaration.moduleSpecifier) {
689+
addRef();
682690
}
683691

684-
if (!propertyName || (propertyName === referenceLocation ? state.markSeenReExportLHS : state.markSeenReExportRHS)(referenceLocation)) {
685-
addReference(referenceLocation, localSymbol, search.location, state);
692+
if (!state.isForRename && state.markSeenReExportRHS(name)) {
693+
addReference(name, referenceSymbol, name, state);
686694
}
687-
688-
const renameExportRHS = propertyName === referenceLocation ? name : undefined;
689-
if (renameExportRHS) {
690-
// For `export { foo as bar }`, rename `foo`, but not `bar`.
691-
if (state.isForRename) {
692-
return;
693-
}
694-
695-
if (state.markSeenReExportRHS(renameExportRHS)) {
696-
addReference(renameExportRHS, referenceSymbol, renameExportRHS, state);
697-
}
695+
}
696+
else {
697+
if (state.markSeenReExportRHS(referenceLocation)) {
698+
addRef();
698699
}
700+
}
699701

702+
// For `export { foo as bar }`, rename `foo`, but not `bar`.
703+
if (!(referenceLocation === propertyName && state.isForRename)) {
700704
const exportKind = (referenceLocation as Identifier).originalKeywordKind === ts.SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named;
701705
const exportInfo = getExportInfo(referenceSymbol, exportKind, state.checker);
702706
Debug.assert(!!exportInfo);
703707
searchForImportsOfExport(referenceLocation, referenceSymbol, exportInfo, state);
704708
}
709+
710+
// At `export { x } from "foo"`, also search for the imported symbol `"foo".x`.
711+
if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
712+
searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
713+
}
714+
715+
function addRef() {
716+
addReference(referenceLocation, localSymbol, search.location, state);
717+
}
705718
}
706719

707720
function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol {

src/services/importTracker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ namespace ts.FindAllReferences {
367367
* Given a local reference, we might notice that it's an import/export and recursively search for references of that.
368368
* If at an import, look locally for the symbol it imports.
369369
* If an an export, look for all imports of it.
370+
* This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`.
370371
* @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here.
371372
*/
372373
export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined {

tests/cases/fourslash/findAllRefsIII.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.ts
4+
////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
5+
6+
// @Filename: /b.ts
7+
////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
8+
9+
//@Filename: /c.ts
10+
////export { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./b";
11+
////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a";
12+
////[|x|];
13+
14+
// @Filename: /d.ts
15+
////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./c";
16+
17+
verify.noErrors();
18+
const [a, b, cFromB, cFromA, cUse, d] = test.ranges();
19+
const cFromARanges = [cFromA, cUse];
20+
21+
const aGroup = { definition: "const x: 0", ranges: [a] };
22+
const cFromAGroup = { definition: "import x", ranges: cFromARanges };
23+
24+
verify.referenceGroups(a, [aGroup, cFromAGroup]);
25+
26+
const bGroup = { definition: "const x: 0", ranges: [b] };
27+
const cFromBGroup = { definition: "import x", ranges: [cFromB] };
28+
const dGroup = { definition: "import x", ranges: [d] };
29+
verify.referenceGroups(b, [bGroup, cFromBGroup, dGroup]);
30+
31+
verify.referenceGroups(cFromB, [cFromBGroup, dGroup, bGroup]);
32+
verify.referenceGroups(cFromARanges, [cFromAGroup, aGroup]);
33+
34+
verify.referenceGroups(d, [dGroup, cFromBGroup, bGroup]);
35+
36+
verify.rangesAreRenameLocations([a, cFromA, cUse]);
37+
verify.rangesAreRenameLocations([b, cFromB, d]);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.ts
4+
////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
5+
6+
//@Filename: /b.ts
7+
////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] as [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a";
8+
////[|x|];
9+
10+
verify.noErrors();
11+
const [r0, r1, r2, r3] = test.ranges();
12+
const aRanges = [r0, r1];
13+
const bRanges = [r2, r3];
14+
const aGroup = { definition: "const x: 0", ranges: aRanges };
15+
const bGroup = { definition: "import x", ranges: bRanges };
16+
verify.referenceGroups(aRanges, [aGroup, bGroup]);
17+
verify.referenceGroups(bRanges, [bGroup]);
18+
19+
verify.rangesAreRenameLocations(aRanges);
20+
verify.rangesAreRenameLocations(aRanges);

0 commit comments

Comments
 (0)