Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;