diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 851d2600fc7eb..f01558a2d4b9e 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1180,7 +1180,9 @@ namespace ts.FindAllReferences { for (const indirectUser of indirectUsers) { for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { // Import specifiers should be handled by importSearches - if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && checker.getSymbolAtLocation(node) === exportSymbol) { + const symbol = checker.getSymbolAtLocation(node); + const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false); + if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) { cb(node); } } diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 43096a4d08134..799df2f176a0e 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -48,7 +48,7 @@ namespace ts.refactor { }); // If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. - type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement; + type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment; interface ExportInfo { readonly exportNode: ExportToConvert; readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. @@ -67,7 +67,8 @@ namespace ts.refactor { const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol; - const flags = getSyntacticModifierFlags(exportNode); + const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None); + const wasDefault = !!(flags & ModifierFlags.Default); // If source file already has a default export, don't offer refactor. if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { @@ -95,6 +96,11 @@ namespace ts.refactor { Debug.assert(!wasDefault, "Can't have a default flag here"); return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined; } + case SyntaxKind.ExportAssignment: { + const node = exportNode as ExportAssignment; + const exp = node.expression as Identifier; + return node.isExportEquals ? undefined : { exportNode: node, exportName: exp, wasDefault, exportingModuleSymbol }; + } default: return undefined; } @@ -107,7 +113,14 @@ namespace ts.refactor { function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { if (wasDefault) { - changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); + if (isExportAssignment(exportNode) && !exportNode.isExportEquals) { + const exp = exportNode.expression as Identifier; + const spec = makeExportSpecifier(exp.text, exp.text); + changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([spec]))); + } + else { + changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); + } } else { const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list"); @@ -134,7 +147,7 @@ namespace ts.refactor { changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text))); break; default: - Debug.assertNever(exportNode, `Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); + Debug.fail(`Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); } } } diff --git a/tests/cases/fourslash/refactorConvertExport_defaultToNamed2.ts b/tests/cases/fourslash/refactorConvertExport_defaultToNamed2.ts new file mode 100644 index 0000000000000..bd18a22183722 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_defaultToNamed2.ts @@ -0,0 +1,44 @@ +/// + +// @Filename: /a.ts +////const f = () => {}; +/////*a*/export default f;/*b*/ + +// @Filename: /b.ts +////import f from "./a"; +////import { default as f } from "./a"; +////import { default as g } from "./a"; +////import f, * as a from "./a"; +//// +////export { default } from "./a"; +////export { default as f } from "./a"; +////export { default as i } from "./a"; +//// +////import * as a from "./a"; +////a.default(); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert export", + actionName: "Convert default export to named export", + actionDescription: "Convert default export to named export", + newContent: { + "/a.ts": +`const f = () => {}; +export { f };`, + + "/b.ts": +`import { f } from "./a"; +import { f } from "./a"; +import { f as g } from "./a"; +import * as a from "./a"; +import { f } from "./a"; + +export { f as default } from "./a"; +export { f } from "./a"; +export { f as i } from "./a"; + +import * as a from "./a"; +a.f();`, +}, +});