diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts
index c48aa6d2316..9da43c1771f 100644
--- a/src/services/findAllReferences.ts
+++ b/src/services/findAllReferences.ts
@@ -235,16 +235,16 @@ namespace ts.FindAllReferences {
markSeenContainingTypeReference(containingTypeReference: Node): boolean;
/**
- * It's possible that we will encounter either side of `export { foo as bar } from "x";` more than once.
+ * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once.
* For example:
- * export { foo as bar } from "a";
- * import { foo } from "a";
+ * // b.ts
+ * export { foo as bar } from "./a";
+ * import { bar } from "./b";
*
* Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local).
* But another reference to it may appear in the same source file.
* See `tests/cases/fourslash/transitiveExportImports3.ts`.
*/
- markSeenReExportLHS(lhs: Identifier): boolean;
markSeenReExportRHS(rhs: Identifier): boolean;
}
@@ -259,7 +259,7 @@ namespace ts.FindAllReferences {
return {
...options,
sourceFiles, isForConstructor, checker, cancellationToken, searchMeaning, inheritsFromCache, getImportSearches, createSearch, referenceAdder, addStringOrCommentReference,
- markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportLHS: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(),
+ markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(),
};
function getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult {
@@ -312,9 +312,7 @@ namespace ts.FindAllReferences {
if (singleReferences.length) {
const addRef = state.referenceAdder(exportSymbol, exportLocation);
for (const singleRef of singleReferences) {
- if (state.markSeenReExportLHS(singleRef)) {
- addRef(singleRef);
- }
+ addRef(singleRef);
}
}
@@ -648,9 +646,15 @@ namespace ts.FindAllReferences {
return;
}
- if (isExportSpecifier(referenceLocation.parent)) {
+ const { parent } = referenceLocation;
+ if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) {
+ // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again.
+ return;
+ }
+
+ if (isExportSpecifier(parent)) {
Debug.assert(referenceLocation.kind === SyntaxKind.Identifier);
- getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, referenceLocation.parent, search, state);
+ getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state);
return;
}
@@ -672,39 +676,48 @@ namespace ts.FindAllReferences {
function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void {
const { parent, propertyName, name } = exportSpecifier;
- searchForExport(getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker));
-
const exportDeclaration = parent.parent;
- if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
- searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
+ const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker);
+ if (!search.includes(localSymbol)) {
+ return;
}
- function searchForExport(localSymbol: Symbol): void {
- if (!search.includes(localSymbol)) {
- return;
+ if (!propertyName) {
+ addRef()
+ }
+ else if (referenceLocation === propertyName) {
+ // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export.
+ // For `export { foo as bar };`, where `foo` is a local, so add it now.
+ if (!exportDeclaration.moduleSpecifier) {
+ addRef();
}
- if (!propertyName || (propertyName === referenceLocation ? state.markSeenReExportLHS : state.markSeenReExportRHS)(referenceLocation)) {
- addReference(referenceLocation, localSymbol, search.location, state);
+ if (!state.isForRename && state.markSeenReExportRHS(name)) {
+ addReference(name, referenceSymbol, name, state);
}
-
- const renameExportRHS = propertyName === referenceLocation ? name : undefined;
- if (renameExportRHS) {
- // For `export { foo as bar }`, rename `foo`, but not `bar`.
- if (state.isForRename) {
- return;
- }
-
- if (state.markSeenReExportRHS(renameExportRHS)) {
- addReference(renameExportRHS, referenceSymbol, renameExportRHS, state);
- }
+ }
+ else {
+ if (state.markSeenReExportRHS(referenceLocation)) {
+ addRef();
}
+ }
+ // For `export { foo as bar }`, rename `foo`, but not `bar`.
+ if (!(referenceLocation === propertyName && state.isForRename)) {
const exportKind = (referenceLocation as Identifier).originalKeywordKind === ts.SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named;
const exportInfo = getExportInfo(referenceSymbol, exportKind, state.checker);
Debug.assert(!!exportInfo);
searchForImportsOfExport(referenceLocation, referenceSymbol, exportInfo, state);
}
+
+ // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`.
+ if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) {
+ searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state);
+ }
+
+ function addRef() {
+ addReference(referenceLocation, localSymbol, search.location, state);
+ }
}
function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol {
diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts
index 3f4e896faf2..154b046dc40 100644
--- a/src/services/importTracker.ts
+++ b/src/services/importTracker.ts
@@ -367,6 +367,7 @@ namespace ts.FindAllReferences {
* Given a local reference, we might notice that it's an import/export and recursively search for references of that.
* If at an import, look locally for the symbol it imports.
* If an an export, look for all imports of it.
+ * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`.
* @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.
*/
export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined {
diff --git a/tests/cases/fourslash/findAllRefsIII.ts b/tests/cases/fourslash/findAllRefsIII.ts
deleted file mode 100644
index 99a5ba55719..00000000000
--- a/tests/cases/fourslash/findAllRefsIII.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-///
-
-// @Filename: /a.ts
-////function [|{| "isWriteAccess": true, "isDefinition": true |}f|]() {};
-////export { [|{| "isWriteAccess": true, "isDefinition": true |}f|] as [|{| "isWriteAccess": true, "isDefinition": true |}g|] };
-
-// @Filename: /b.ts
-////import { [|{| "isWriteAccess": true, "isDefinition": true |}g|] } from "./a";
-
-verify.noErrors();
-const [f0, f1, g0, g1] = test.ranges();
-
-const fs = { definition: "function f(): void", ranges: [f0, f1] };
-const gs0 = { definition: "import g", ranges: [g0] };
-const gs1 = { definition: "import g", ranges: [g1] };
-verify.referenceGroups(f0, [fs, gs0, gs1]);
diff --git a/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts b/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts
new file mode 100644
index 00000000000..c9d42e53d4e
--- /dev/null
+++ b/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /a.ts
+////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
+
+// @Filename: /b.ts
+////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
+
+//@Filename: /c.ts
+////export { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./b";
+////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a";
+////[|x|];
+
+// @Filename: /d.ts
+////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./c";
+
+verify.noErrors();
+const [a, b, cFromB, cFromA, cUse, d] = test.ranges();
+const cFromARanges = [cFromA, cUse];
+
+const aGroup = { definition: "const x: 0", ranges: [a] };
+const cFromAGroup = { definition: "import x", ranges: cFromARanges };
+
+verify.referenceGroups(a, [aGroup, cFromAGroup]);
+
+const bGroup = { definition: "const x: 0", ranges: [b] };
+const cFromBGroup = { definition: "import x", ranges: [cFromB] };
+const dGroup = { definition: "import x", ranges: [d] };
+verify.referenceGroups(b, [bGroup, cFromBGroup, dGroup]);
+
+verify.referenceGroups(cFromB, [cFromBGroup, dGroup, bGroup]);
+verify.referenceGroups(cFromARanges, [cFromAGroup, aGroup]);
+
+verify.referenceGroups(d, [dGroup, cFromBGroup, bGroup]);
+
+verify.rangesAreRenameLocations([a, cFromA, cUse]);
+verify.rangesAreRenameLocations([b, cFromB, d]);
diff --git a/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts b/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts
new file mode 100644
index 00000000000..c1a49b3b5b0
--- /dev/null
+++ b/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts
@@ -0,0 +1,20 @@
+///
+
+// @Filename: /a.ts
+////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0;
+
+//@Filename: /b.ts
+////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] as [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a";
+////[|x|];
+
+verify.noErrors();
+const [r0, r1, r2, r3] = test.ranges();
+const aRanges = [r0, r1];
+const bRanges = [r2, r3];
+const aGroup = { definition: "const x: 0", ranges: aRanges };
+const bGroup = { definition: "import x", ranges: bRanges };
+verify.referenceGroups(aRanges, [aGroup, bGroup]);
+verify.referenceGroups(bRanges, [bGroup]);
+
+verify.rangesAreRenameLocations(aRanges);
+verify.rangesAreRenameLocations(aRanges);