fix(transformer): TS transform generate do not copy statements (#3832)

Remove an unsound usage of `ast.copy` from TS transform. Previously the same statements were inserted into the AST multiple times. Instead generate these statements on demand.

Also use a `std::vec::Vec` for temporary values, rather than allocating them into arena, where they take up space to no purpose.
This commit is contained in:
overlookmotel 2024-06-23 08:45:24 +00:00
parent d5f6aeb1ca
commit d774e54f8e
3 changed files with 83 additions and 60 deletions

View file

@ -208,17 +208,17 @@ impl<'a> Traverse<'a> for Transformer<'a> {
fn enter_method_definition(
&mut self,
def: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
_ctx: &mut TraverseCtx<'a>,
) {
self.x0_typescript.transform_method_definition(def, ctx);
self.x0_typescript.transform_method_definition(def);
}
fn exit_method_definition(
&mut self,
def: &mut MethodDefinition<'a>,
_ctx: &mut TraverseCtx<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x0_typescript.transform_method_definition_on_exit(def);
self.x0_typescript.transform_method_definition_on_exit(def, ctx);
}
fn enter_new_expression(&mut self, expr: &mut NewExpression<'a>, _ctx: &mut TraverseCtx<'a>) {
@ -238,8 +238,8 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x3_es2015.enter_statements(stmts);
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements_on_exit(stmts);
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements_on_exit(stmts, ctx);
self.x3_es2015.exit_statements(stmts);
}

View file

@ -2,10 +2,10 @@
use std::rc::Rc;
use oxc_allocator::Vec;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_span::{Atom, GetSpan, SPAN};
use oxc_syntax::{operator::AssignmentOperator, reference::ReferenceFlag};
use oxc_span::{Atom, GetSpan, Span, SPAN};
use oxc_syntax::{operator::AssignmentOperator, reference::ReferenceFlag, symbol::SymbolId};
use oxc_traverse::TraverseCtx;
use rustc_hash::FxHashSet;
@ -16,7 +16,7 @@ pub struct TypeScriptAnnotations<'a> {
options: Rc<TypeScriptOptions>,
ctx: Ctx<'a>,
/// Assignments to be added to the constructor body
assignments: Vec<'a, Statement<'a>>,
assignments: Vec<Assignment<'a>>,
has_super_call: bool,
has_jsx_element: bool,
@ -42,7 +42,7 @@ impl<'a> TypeScriptAnnotations<'a> {
Self {
has_super_call: false,
assignments: ctx.ast.new_vec(),
assignments: vec![],
options,
ctx,
has_jsx_element: false,
@ -60,35 +60,6 @@ impl<'a> TypeScriptAnnotations<'a> {
|| self.has_jsx_fragment && name == self.jsx_fragment_import_name
}
// Creates `this.name = name`
fn create_this_property_assignment(
&self,
id: &BindingIdentifier<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Statement<'a> {
let ast = self.ctx.ast;
let symbol_id = id.symbol_id.get().unwrap();
let reference_id =
ctx.create_bound_reference(id.name.to_compact_str(), symbol_id, ReferenceFlag::Read);
let id = IdentifierReference::new_read(id.span, id.name.clone(), Some(reference_id));
ast.expression_statement(
SPAN,
ast.assignment_expression(
SPAN,
AssignmentOperator::Assign,
ast.simple_assignment_target_member_expression(ast.static_member(
SPAN,
ast.this_expression(SPAN),
IdentifierName::new(id.span, id.name.clone()),
false,
)),
ast.identifier_reference_expression(id),
),
)
}
// Remove type only imports/exports
pub fn transform_program_on_exit(
&mut self,
@ -306,19 +277,18 @@ impl<'a> TypeScriptAnnotations<'a> {
elem.type_parameters = None;
}
pub fn transform_method_definition(
&mut self,
def: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
pub fn transform_method_definition(&mut self, def: &mut MethodDefinition<'a>) {
// Collects parameter properties so that we can add an assignment
// for each of them in the constructor body.
if def.kind == MethodDefinitionKind::Constructor {
for param in def.value.params.items.as_mut_slice() {
if param.is_public() {
if let Some(id) = param.pattern.get_binding_identifier() {
let assignment = self.create_this_property_assignment(id, ctx);
self.assignments.push(assignment);
self.assignments.push(Assignment {
span: id.span,
name: id.name.clone(),
symbol_id: id.symbol_id.get().unwrap(),
});
}
}
@ -333,7 +303,11 @@ impl<'a> TypeScriptAnnotations<'a> {
def.r#override = false;
}
pub fn transform_method_definition_on_exit(&mut self, def: &mut MethodDefinition<'a>) {
pub fn transform_method_definition_on_exit(
&mut self,
def: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if def.kind == MethodDefinitionKind::Constructor && !self.assignments.is_empty() {
// When the constructor doesn't have a super call,
// we simply add assignments to the bottom of the function body
@ -350,7 +324,11 @@ impl<'a> TypeScriptAnnotations<'a> {
)
})
.statements
.extend(self.assignments.drain(..));
.extend(
self.assignments
.drain(..)
.map(|assignment| assignment.create_this_property_assignment(ctx)),
);
}
}
}
@ -379,7 +357,7 @@ impl<'a> TypeScriptAnnotations<'a> {
def.type_annotation = None;
}
pub fn transform_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
pub fn transform_statements(&mut self, stmts: &mut ArenaVec<'a, Statement<'a>>) {
// Remove declare declaration
stmts.retain(|stmt| match stmt {
match_declaration!(Statement) => {
@ -389,7 +367,11 @@ impl<'a> TypeScriptAnnotations<'a> {
});
}
pub fn transform_statements_on_exit(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
pub fn transform_statements_on_exit(
&mut self,
stmts: &mut ArenaVec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
// Remove TS specific statements
stmts.retain(|stmt| match stmt {
Statement::ExpressionStatement(s) => !s.expression.is_typescript_syntax(),
@ -410,7 +392,11 @@ impl<'a> TypeScriptAnnotations<'a> {
let is_super_call = matches!(stmt, Statement::ExpressionStatement(ref stmt) if stmt.expression.is_super_call_expression());
new_stmts.push(stmt);
if is_super_call {
new_stmts.extend(self.ctx.ast.copy(&self.assignments));
new_stmts.extend(
self.assignments
.iter()
.map(|assignment| assignment.create_this_property_assignment(ctx)),
);
}
}
self.has_super_call = true;
@ -528,3 +514,36 @@ impl<'a> TypeScriptAnnotations<'a> {
self.is_jsx_imports(name)
}
}
struct Assignment<'a> {
span: Span,
name: Atom<'a>,
symbol_id: SymbolId,
}
impl<'a> Assignment<'a> {
// Creates `this.name = name`
fn create_this_property_assignment(&self, ctx: &mut TraverseCtx<'a>) -> Statement<'a> {
let reference_id = ctx.create_bound_reference(
self.name.to_compact_str(),
self.symbol_id,
ReferenceFlag::Read,
);
let id = IdentifierReference::new_read(self.span, self.name.clone(), Some(reference_id));
ctx.ast.expression_statement(
SPAN,
ctx.ast.assignment_expression(
SPAN,
AssignmentOperator::Assign,
ctx.ast.simple_assignment_target_member_expression(ctx.ast.static_member(
SPAN,
ctx.ast.this_expression(SPAN),
IdentifierName::new(self.span, self.name.clone()),
false,
)),
ctx.ast.identifier_reference_expression(id),
),
)
}
}

View file

@ -127,16 +127,16 @@ impl<'a> TypeScript<'a> {
self.annotations.transform_jsx_opening_element(elem);
}
pub fn transform_method_definition(
pub fn transform_method_definition(&mut self, def: &mut MethodDefinition<'a>) {
self.annotations.transform_method_definition(def);
}
pub fn transform_method_definition_on_exit(
&mut self,
def: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.annotations.transform_method_definition(def, ctx);
}
pub fn transform_method_definition_on_exit(&mut self, def: &mut MethodDefinition<'a>) {
self.annotations.transform_method_definition_on_exit(def);
self.annotations.transform_method_definition_on_exit(def, ctx);
}
pub fn transform_new_expression(&mut self, expr: &mut NewExpression<'a>) {
@ -151,8 +151,12 @@ impl<'a> TypeScript<'a> {
self.annotations.transform_statements(stmts);
}
pub fn transform_statements_on_exit(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
self.annotations.transform_statements_on_exit(stmts);
pub fn transform_statements_on_exit(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
self.annotations.transform_statements_on_exit(stmts, ctx);
}
pub fn transform_statement(&mut self, stmt: &mut Statement<'a>, ctx: &TraverseCtx<'a>) {