diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index f53b7ffad49..7861f28402a 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -103,23 +103,17 @@ namespace ts.FindAllReferences { break; // TODO: GH#23879 case SyntaxKind.ImportEqualsDeclaration: - handleNamespaceImport(direct, direct.name, hasModifier(direct, ModifierFlags.Export)); + handleNamespaceImport(direct, direct.name, hasModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); break; case SyntaxKind.ImportDeclaration: + directImports.push(direct); const namedBindings = direct.importClause && direct.importClause.namedBindings; if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { - handleNamespaceImport(direct, namedBindings.name); + handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); } - else if (isDefaultImport(direct)) { - const sourceFileLike = getSourceFileLikeForImportDeclaration(direct); - if (!isAvailableThroughGlobal) { - addIndirectUser(sourceFileLike); // Add a check for indirect uses to handle synthetic default imports - } - directImports.push(direct); - } - else { - directImports.push(direct); + else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { + addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports } break; @@ -145,10 +139,10 @@ namespace ts.FindAllReferences { } } - function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport?: boolean): void { + function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { if (exportKind === ExportKind.ExportEquals) { // This is a direct import, not import-as-namespace. - directImports.push(importDeclaration); + if (!alreadyAddedDirect) directImports.push(importDeclaration); } else if (!isAvailableThroughGlobal) { const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); @@ -247,34 +241,30 @@ namespace ts.FindAllReferences { return; } - const { importClause } = decl; - if (!importClause) { - return; - } + const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; - const { namedBindings } = importClause; - if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { - handleNamespaceImportLike(namedBindings.name); - return; - } - - if (exportKind === ExportKind.Named) { - searchForNamedImport(namedBindings as NamedImports | undefined); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217) - } - else { - // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals - const { name } = importClause; - // If a default import has the same name as the default export, allow to rename it. - // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. - if (name && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { - const defaultImportAlias = checker.getSymbolAtLocation(name)!; - addSearch(name, defaultImportAlias); + if (namedBindings) { + switch (namedBindings.kind) { + case SyntaxKind.NamespaceImport: + handleNamespaceImportLike(namedBindings.name); + break; + case SyntaxKind.NamedImports: + // 'default' might be accessed as a named import `{ default as foo }`. + if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { + searchForNamedImport(namedBindings); + } + break; + default: + Debug.assertNever(namedBindings); } + } - // 'default' might be accessed as a named import `{ default as foo }`. - if (exportKind === ExportKind.Default) { - searchForNamedImport(namedBindings); - } + // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. + // If a default import has the same name as the default export, allow to rename it. + // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. + if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { + const defaultImportAlias = checker.getSymbolAtLocation(name)!; + addSearch(name, defaultImportAlias); } } diff --git a/tests/cases/fourslash/findAllRefsDefaultImport.ts b/tests/cases/fourslash/findAllRefsDefaultImport.ts new file mode 100644 index 00000000000..b981f50bd16 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsDefaultImport.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +////export default function [|{| "isWriteAccess": true, "isDefinition": true |}a|]() {} + +// @Filename: /b.ts +////import [|{| "isWriteAccess": true, "isDefinition": true |}a|], * as ns from "./a"; + +const [r0, r1] = test.ranges(); +const a: FourSlashInterface.ReferenceGroup = { definition: "function a(): void", ranges: [r0] }; +const b: FourSlashInterface.ReferenceGroup = { definition: "(alias) function a(): void\nimport a", ranges: [r1] }; +verify.referenceGroups(r0, [a, b]); +verify.referenceGroups(r1, [b, a]); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a104e2fde0f..a70d6dcf424 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -255,7 +255,7 @@ declare namespace FourSlashInterface { * For each of starts, asserts the ranges that are referenced from there. * This uses the 'findReferences' command instead of 'getReferencesAtPosition', so references are grouped by their definition. */ - referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: Array<{ definition: ReferencesDefinition, ranges: Range[] }>): void; + referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: Array): void; singleReferenceGroup(definition: ReferencesDefinition, ranges?: Range[]): void; rangesAreOccurrences(isWriteAccess?: boolean): void; rangesWithSameTextAreRenameLocations(): void; @@ -511,6 +511,10 @@ declare namespace FourSlashInterface { text: string; range: Range; } + interface ReferenceGroup { + readonly definition: ReferencesDefinition; + readonly ranges: ReadonlyArray; + } interface Diagnostic { message: string; /** @default `test.ranges()[0]` */