diff --git a/crates/oxc_isolated_declarations/src/lib.rs b/crates/oxc_isolated_declarations/src/lib.rs index 35a8db768fcd0..2a834c0e5f267 100644 --- a/crates/oxc_isolated_declarations/src/lib.rs +++ b/crates/oxc_isolated_declarations/src/lib.rs @@ -135,15 +135,7 @@ impl<'a> IsolatedDeclarations<'a> { &mut self, program: &Program<'a>, ) -> oxc_allocator::Vec<'a, Statement<'a>> { - let has_import_or_export = program.body.iter().any(|stmt| { - matches!( - stmt, - Statement::ImportDeclaration(_) - | Statement::ExportAllDeclaration(_) - | Statement::ExportDefaultDeclaration(_) - | Statement::ExportNamedDeclaration(_) - ) - }); + let has_import_or_export = program.body.iter().any(Statement::is_module_declaration); if has_import_or_export { self.transform_statements_on_demand(&program.body) @@ -195,7 +187,9 @@ impl<'a> IsolatedDeclarations<'a> { let mut transformed_stmts: FxHashMap> = FxHashMap::default(); let mut transformed_variable_declarator: FxHashMap> = FxHashMap::default(); - let mut export_default_var = None; + // When transforming `export default` with expression or `export = expression`, + // we will emit an extra variable declaration to store the inferred type of expression + let mut extra_export_var_statement = None; // 1. Collect all declarations, module declarations // 2. Transform export declarations @@ -233,25 +227,35 @@ impl<'a> IsolatedDeclarations<'a> { } match_module_declaration!(Statement) => { match stmt.to_module_declaration() { + ModuleDeclaration::TSExportAssignment(decl) => { + transformed_spans.insert(decl.span); + if let Some((var_decl, new_decl)) = + self.transform_ts_export_assignment(decl) + { + if let Some(var_decl) = var_decl { + self.scope.visit_statement(&var_decl); + extra_export_var_statement = Some(var_decl); + } + + self.scope.visit_statement(&new_decl); + transformed_stmts.insert(decl.span, new_decl); + } else { + self.scope.visit_ts_export_assignment(decl); + } + need_empty_export_marker = false; + } ModuleDeclaration::ExportDefaultDeclaration(decl) => { transformed_spans.insert(decl.span); if let Some((var_decl, new_decl)) = self.transform_export_default_declaration(decl) { if let Some(var_decl) = var_decl { - self.scope.visit_variable_declaration(&var_decl); - export_default_var = Some(Statement::VariableDeclaration( - self.ast.alloc(var_decl), - )); + self.scope.visit_statement(&var_decl); + extra_export_var_statement = Some(var_decl); } - self.scope.visit_export_default_declaration(&new_decl); - transformed_stmts.insert( - decl.span, - Statement::from(ModuleDeclaration::ExportDefaultDeclaration( - self.ast.alloc(new_decl), - )), - ); + self.scope.visit_statement(&new_decl); + transformed_stmts.insert(decl.span, new_decl); } else { self.scope.visit_export_default_declaration(decl); } @@ -358,16 +362,20 @@ impl<'a> IsolatedDeclarations<'a> { } // 6. Transform variable/using declarations, import statements, remove unused imports - let mut new_stm = - self.ast.vec_with_capacity(stmts.len() + usize::from(export_default_var.is_some())); + let mut new_stm = self + .ast + .vec_with_capacity(stmts.len() + usize::from(extra_export_var_statement.is_some())); stmts.iter().for_each(|stmt| { if transformed_spans.contains(&stmt.span()) { let new_stmt = transformed_stmts .remove(&stmt.span()) .unwrap_or_else(|| stmt.clone_in(self.ast.allocator)); - if matches!(new_stmt, Statement::ExportDefaultDeclaration(_)) { - if let Some(export_default_var) = export_default_var.take() { - new_stm.push(export_default_var); + if matches!( + new_stmt, + Statement::ExportDefaultDeclaration(_) | Statement::TSExportAssignment(_) + ) { + if let Some(export_external_var_statement) = extra_export_var_statement.take() { + new_stm.push(export_external_var_statement); } } new_stm.push(new_stmt); diff --git a/crates/oxc_isolated_declarations/src/module.rs b/crates/oxc_isolated_declarations/src/module.rs index 5746c37dc6c96..a905fe717dae6 100644 --- a/crates/oxc_isolated_declarations/src/module.rs +++ b/crates/oxc_isolated_declarations/src/module.rs @@ -35,7 +35,7 @@ impl<'a> IsolatedDeclarations<'a> { pub(crate) fn transform_export_default_declaration( &mut self, decl: &ExportDefaultDeclaration<'a>, - ) -> Option<(Option>, ExportDefaultDeclaration<'a>)> { + ) -> Option<(Option>, Statement<'a>)> { let declaration = match &decl.declaration { ExportDefaultDeclarationKind::FunctionDeclaration(decl) => self .transform_function(decl, Some(false)) @@ -47,46 +47,65 @@ impl<'a> IsolatedDeclarations<'a> { // SAFETY: `ast.copy` is unsound! We need to fix. Some((None, unsafe { self.ast.copy(&decl.declaration) })) } - expr @ match_expression!(ExportDefaultDeclarationKind) => { - let expr = expr.to_expression(); - if matches!(expr, Expression::Identifier(_)) { - None - } else { - // declare const _default: Type - let kind = VariableDeclarationKind::Const; - let name = self.create_unique_name("_default"); - let id = self.ast.binding_pattern_kind_binding_identifier(SPAN, &name); - let type_annotation = self - .infer_type_from_expression(expr) - .map(|ts_type| self.ast.ts_type_annotation(SPAN, ts_type)); - - if type_annotation.is_none() { - self.error(default_export_inferred(expr.span())); - } - - let id = self.ast.binding_pattern(id, type_annotation, false); - let declarations = - self.ast.vec1(self.ast.variable_declarator(SPAN, kind, id, None, false)); - - Some(( - Some(self.ast.variable_declaration( - SPAN, - kind, - declarations, - self.is_declare(), - )), - ExportDefaultDeclarationKind::from( - self.ast.expression_identifier_reference(SPAN, &name), - ), - )) - } - } + declaration @ match_expression!(ExportDefaultDeclarationKind) => self + .transform_export_expression(declaration.to_expression()) + .map(|(var_decl, expr)| (var_decl, ExportDefaultDeclarationKind::from(expr))), }; declaration.map(|(var_decl, declaration)| { let exported = ModuleExportName::IdentifierName(self.ast.identifier_name(SPAN, "default")); - (var_decl, self.ast.export_default_declaration(decl.span, declaration, exported)) + let declaration = self.ast.module_declaration_export_default_declaration( + decl.span, + declaration, + exported, + ); + (var_decl, Statement::from(declaration)) + }) + } + + fn transform_export_expression( + &mut self, + expr: &Expression<'a>, + ) -> Option<(Option>, Expression<'a>)> { + if matches!(expr, Expression::Identifier(_)) { + None + } else { + // declare const _default: Type + let kind = VariableDeclarationKind::Const; + let name = self.create_unique_name("_default"); + let id = self.ast.binding_pattern_kind_binding_identifier(SPAN, &name); + let type_annotation = self + .infer_type_from_expression(expr) + .map(|ts_type| self.ast.ts_type_annotation(SPAN, ts_type)); + + if type_annotation.is_none() { + self.error(default_export_inferred(expr.span())); + } + + let id = self.ast.binding_pattern(id, type_annotation, false); + let declarations = + self.ast.vec1(self.ast.variable_declarator(SPAN, kind, id, None, false)); + + let variable_statement = Statement::from(self.ast.declaration_variable( + SPAN, + kind, + declarations, + self.is_declare(), + )); + Some((Some(variable_statement), self.ast.expression_identifier_reference(SPAN, &name))) + } + } + + pub(crate) fn transform_ts_export_assignment( + &mut self, + decl: &TSExportAssignment<'a>, + ) -> Option<(Option>, Statement<'a>)> { + self.transform_export_expression(&decl.expression).map(|(var_decl, expr)| { + ( + var_decl, + Statement::from(self.ast.module_declaration_ts_export_assignment(decl.span, expr)), + ) }) } @@ -136,7 +155,7 @@ impl<'a> IsolatedDeclarations<'a> { /// const a = 1; /// function b() {} /// ``` - pub fn strip_export_keyword(&self, stmts: &mut Vec<'a, Statement<'a>>) { + pub(crate) fn strip_export_keyword(&self, stmts: &mut Vec<'a, Statement<'a>>) { stmts.iter_mut().for_each(|stmt| { if let Statement::ExportNamedDeclaration(decl) = stmt { if let Some(declaration) = &mut decl.declaration { diff --git a/crates/oxc_isolated_declarations/tests/fixtures/ts-export-assignment.ts b/crates/oxc_isolated_declarations/tests/fixtures/ts-export-assignment.ts new file mode 100644 index 0000000000000..fff3b74319a06 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/ts-export-assignment.ts @@ -0,0 +1,5 @@ +const Res = 0; + +export = function Foo(): typeof Res { + return Res; +} \ No newline at end of file diff --git a/crates/oxc_isolated_declarations/tests/snapshots/ts-export-assignment.snap b/crates/oxc_isolated_declarations/tests/snapshots/ts-export-assignment.snap new file mode 100644 index 0000000000000..71b2d8253c0f7 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/ts-export-assignment.snap @@ -0,0 +1,10 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/ts-export-assignment.ts +--- +``` +==================== .D.TS ==================== + +declare const Res = 0; +declare const _default: () => typeof Res; +export = _default;