Skip to content

Commit d99a46e

Browse files
author
Andy Hanson
committed
Better handle additional re-export cases
1 parent 42a832a commit d99a46e

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

@@ -648,9 +646,15 @@ namespace ts.FindAllReferences {
648646
return;
649647
}
650648

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

@@ -672,39 +676,48 @@ namespace ts.FindAllReferences {
672676

673677
function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void {
674678
const { parent, propertyName, name } = exportSpecifier;
675-
searchForExport(getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker));
676-
677679
const exportDeclaration = parent.parent;
678-
if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
679-
searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
680+
const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker);
681+
if (!search.includes(localSymbol)) {
682+
return;
680683
}
681684

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

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

705+
// For `export { foo as bar }`, rename `foo`, but not `bar`.
706+
if (!(referenceLocation === propertyName && state.isForRename)) {
703707
const exportKind = (referenceLocation as Identifier).originalKeywordKind === ts.SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named;
704708
const exportInfo = getExportInfo(referenceSymbol, exportKind, state.checker);
705709
Debug.assert(!!exportInfo);
706710
searchForImportsOfExport(referenceLocation, referenceSymbol, exportInfo, state);
707711
}
712+
713+
// At `export { x } from "foo"`, also search for the imported symbol `"foo".x`.
714+
if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
715+
searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
716+
}
717+
718+
function addRef() {
719+
addReference(referenceLocation, localSymbol, search.location, state);
720+
}
708721
}
709722

710723
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)