diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 11afc95db9b2f..0c766fbde12c9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6397,6 +6397,10 @@ "category": "Message", "code": 95163 }, + "Can only convert named export": { + "category": "Message", + "code": 95164 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 799df2f176a0e..24cbd09283da9 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -57,7 +57,7 @@ namespace ts.refactor { }; function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { - const { file } = context; + const { file, program } = context; const span = getRefactorContextSpan(context); const token = getTokenAtPosition(file, span.start); const exportNode = !!(token.parent && getSyntacticModifierFlags(token.parent) & ModifierFlags.Export) && considerPartialSpans ? token.parent : getParentNodeInSpan(token, file, span); @@ -75,6 +75,11 @@ namespace ts.refactor { return { error: getLocaleSpecificMessage(Diagnostics.This_file_already_has_a_default_export) }; } + const checker = program.getTypeChecker(); + const noSymbolError = (id: Node) => + (isIdentifier(id) && checker.getSymbolAtLocation(id)) ? undefined + : { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_named_export) }; + switch (exportNode.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: @@ -83,7 +88,9 @@ namespace ts.refactor { case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ModuleDeclaration: { const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; - return node.name && isIdentifier(node.name) ? { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } : undefined; + if (!node.name) return undefined; + return noSymbolError(node.name) + || { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol }; } case SyntaxKind.VariableStatement: { const vs = exportNode as VariableStatement; @@ -94,12 +101,14 @@ namespace ts.refactor { const decl = first(vs.declarationList.declarations); if (!decl.initializer) return undefined; Debug.assert(!wasDefault, "Can't have a default flag here"); - return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined; + return noSymbolError(decl.name) + || { exportNode: vs, exportName: decl.name as Identifier, wasDefault, exportingModuleSymbol }; } case SyntaxKind.ExportAssignment: { const node = exportNode as ExportAssignment; - const exp = node.expression as Identifier; - return node.isExportEquals ? undefined : { exportNode: node, exportName: exp, wasDefault, exportingModuleSymbol }; + if (node.isExportEquals) return undefined; + return noSymbolError(node.expression) + || { exportNode: node, exportName: node.expression as Identifier, wasDefault, exportingModuleSymbol }; } default: return undefined; diff --git a/tests/cases/fourslash/refactorConvertExport_onlyValidIdentifiers.ts b/tests/cases/fourslash/refactorConvertExport_onlyValidIdentifiers.ts new file mode 100644 index 0000000000000..630d95e72603a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_onlyValidIdentifiers.ts @@ -0,0 +1,25 @@ +/// + +// @allowJs: true + +// @Filename: /a.js +/////*[| |]*/ /** x */ export default 1; + +// @Filename: /b.js +/////*[| |]*/ /** x */ export default (1); + +// @Filename: /c.js +/////*[| |]*/ /** x */ export default x; + +goTo.eachRange(r => { + goTo.selectRange(r); + verify.not.refactorAvailable("Convert export"); +}); + +// goTo.selectRange(test.ranges()[0]); +// edit.applyRefactor({ +// refactorName: "Convert export", +// actionName: "Convert default export to named export", +// actionDescription: "Convert default export to named export", +// newContent: { "/a.js": `...` }, +// });