feat(isolated-declarations): support transform TSExportAssignment declaration (#7204)

part of #7141
This commit is contained in:
Dunqing 2024-11-08 11:51:40 +00:00
parent 4a515be24e
commit b74686c598
4 changed files with 105 additions and 63 deletions

View file

@ -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<Span, Statement<'a>> = FxHashMap::default();
let mut transformed_variable_declarator: FxHashMap<Span, VariableDeclarator<'a>> =
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);

View file

@ -35,7 +35,7 @@ impl<'a> IsolatedDeclarations<'a> {
pub(crate) fn transform_export_default_declaration(
&mut self,
decl: &ExportDefaultDeclaration<'a>,
) -> Option<(Option<VariableDeclaration<'a>>, ExportDefaultDeclaration<'a>)> {
) -> Option<(Option<Statement<'a>>, 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<Statement<'a>>, 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>>, 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 {

View file

@ -0,0 +1,5 @@
const Res = 0;
export = function Foo(): typeof Res {
return Res;
}

View file

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