Skip to content

Commit

Permalink
feat(isolated-declarations): support transform TSExportAssignment dec…
Browse files Browse the repository at this point in the history
…laration (#7204)

part of #7141
  • Loading branch information
Dunqing committed Nov 8, 2024
1 parent 4a515be commit b74686c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 63 deletions.
60 changes: 34 additions & 26 deletions crates/oxc_isolated_declarations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
93 changes: 56 additions & 37 deletions crates/oxc_isolated_declarations/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)),
)
})
}

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Res = 0;

export = function Foo(): typeof Res {
return Res;
}
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit b74686c

Please sign in to comment.