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