Skip to content

Commit 986c48e

Browse files
committed
fix(transformer/decorator): transformed decorators should be injected after class-properties has run (oxc-project#12418)
* close rolldown/rolldown#5374 Example: Input: ```ts function myPropertyDecorator() { return (target: any, propertyKey: number) => { console.log(target.constructor.modelName, propertyKey); }; } class Test { public static modelName = 'Test'; @myPropertyDecorator() public myProperty = 1; } const instance = new Test(); export default instance; ``` Output diff: ```diff function myPropertyDecorator() { return (target, propertyKey) => { console.log(target.constructor.modelName, propertyKey); }; } class Test { constructor() { babelHelpers.defineProperty(this, "myProperty", 1); } } -babelHelpers.decorate([myPropertyDecorator()], Test.prototype, "myProperty", void 0); -babelHelpers.defineProperty(Test, "modelName", "Test"); +babelHelpers.defineProperty(Test, "modelName", "Test"); +babelHelpers.decorate([myPropertyDecorator()], Test.prototype, "myProperty", void 0); const instance = new Test(); export default instance; ``` The cause is that we transform class fields and decorators in two different plugins, and the decorator plugin should be transformed first, and then run the class-properties plugin, because class-properties might erase some decorator information. However, this cause transformed decorators injected before transformed class fields. This PR fixes this problem by collecting all transformed decorators that wait for injection, and injecting them in batch after the `class-properties` plugin has run.
1 parent 6838948 commit 986c48e

File tree

8 files changed

+158
-135
lines changed

8 files changed

+158
-135
lines changed

crates/oxc_transformer/src/decorator/legacy/mod.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use oxc_semantic::{ScopeFlags, SymbolFlags};
5454
use oxc_span::SPAN;
5555
use oxc_syntax::operator::AssignmentOperator;
5656
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse};
57+
use rustc_hash::FxHashMap;
5758

5859
use crate::{
5960
Helper,
@@ -92,6 +93,8 @@ pub struct LegacyDecorator<'a, 'ctx> {
9293
/// we have to transforms decorators to `exit_class` otherwise after class is being transformed by
9394
/// `class-properties` plugin, the decorators' nodes might be lost.
9495
class_decorated_data: Option<ClassDecoratedData<'a>>,
96+
/// Transformed decorators, they will be inserted in the statements at [`Self::exit_class_at_end`].
97+
decorations: FxHashMap<Address, Vec<Statement<'a>>>,
9598
ctx: &'ctx TransformCtx<'a>,
9699
}
97100

@@ -102,6 +105,7 @@ impl<'a, 'ctx> LegacyDecorator<'a, 'ctx> {
102105
metadata: LegacyDecoratorMetadata::new(ctx),
103106
class_decorated_data: None,
104107
ctx,
108+
decorations: FxHashMap::default(),
105109
}
106110
}
107111
}
@@ -492,8 +496,8 @@ impl<'a> LegacyDecorator<'a, '_> {
492496
};
493497

494498
if has_private_in_expression_in_decorator {
495-
let stmts = mem::replace(&mut decoration_stmts, ctx.ast.vec());
496-
Self::insert_decorations_into_class_static_block(class, stmts, ctx);
499+
let decorations = mem::take(&mut decoration_stmts);
500+
Self::insert_decorations_into_class_static_block(class, decorations, ctx);
497501
} else {
498502
let address = match ctx.parent() {
499503
Ancestor::ExportDefaultDeclarationDeclaration(_)
@@ -503,7 +507,7 @@ impl<'a> LegacyDecorator<'a, '_> {
503507
};
504508

505509
decoration_stmts.push(constructor_decoration);
506-
self.ctx.statement_injector.insert_many_after(&address, decoration_stmts);
510+
self.decorations.entry(address).or_default().append(&mut decoration_stmts);
507511
self.class_decorated_data = Some(ClassDecoratedData {
508512
binding: class_binding,
509513
// If the class alias has reassigned to `this` in the static block, then
@@ -561,7 +565,7 @@ impl<'a> LegacyDecorator<'a, '_> {
561565

562566
/// Transforms a non-decorated class declaration.
563567
fn transform_class_declaration_without_class_decorators(
564-
&self,
568+
&mut self,
565569
class: &mut Class<'a>,
566570
has_private_in_expression_in_decorator: bool,
567571
ctx: &mut TraverseCtx<'a>,
@@ -575,7 +579,7 @@ impl<'a> LegacyDecorator<'a, '_> {
575579
class_binding
576580
};
577581

578-
let decoration_stmts =
582+
let mut decoration_stmts =
579583
self.transform_decorators_of_class_elements(class, &class_binding, ctx);
580584

581585
if has_private_in_expression_in_decorator {
@@ -587,7 +591,7 @@ impl<'a> LegacyDecorator<'a, '_> {
587591
// `Class` is always stored in a `Box`, so has a stable memory location
588592
_ => Address::from_ptr(class),
589593
};
590-
self.ctx.statement_injector.insert_many_after(&stmt_address, decoration_stmts);
594+
self.decorations.entry(stmt_address).or_default().append(&mut decoration_stmts);
591595
}
592596
}
593597

@@ -598,8 +602,8 @@ impl<'a> LegacyDecorator<'a, '_> {
598602
class: &mut Class<'a>,
599603
class_binding: &BoundIdentifier<'a>,
600604
ctx: &mut TraverseCtx<'a>,
601-
) -> ArenaVec<'a, Statement<'a>> {
602-
let mut decoration_stmts = ctx.ast.vec_with_capacity(class.body.body.len());
605+
) -> Vec<Statement<'a>> {
606+
let mut decoration_stmts = Vec::with_capacity(class.body.body.len());
603607

604608
for element in &mut class.body.body {
605609
let (is_static, key, descriptor, decorations) = match element {
@@ -738,10 +742,11 @@ impl<'a> LegacyDecorator<'a, '_> {
738742
/// ```
739743
fn insert_decorations_into_class_static_block(
740744
class: &mut Class<'a>,
741-
decorations: ArenaVec<'a, Statement<'a>>,
745+
decorations: Vec<Statement<'a>>,
742746
ctx: &mut TraverseCtx<'a>,
743747
) {
744748
let scope_id = ctx.create_child_scope(class.scope_id(), ScopeFlags::ClassStaticBlock);
749+
let decorations = ctx.ast.vec_from_iter(decorations);
745750
let element = ctx.ast.class_element_static_block_with_scope_id(SPAN, decorations, scope_id);
746751
class.body.body.push(element);
747752
}
@@ -780,6 +785,14 @@ impl<'a> LegacyDecorator<'a, '_> {
780785
}
781786
}
782787

788+
/// Injects the class decorator statements after class-properties plugin has run, ensuring that
789+
/// all transformed fields are injected before the class decorator statements.
790+
pub fn exit_class_at_end(&mut self, _class: &mut Class<'a>, _ctx: &mut TraverseCtx<'a>) {
791+
for (address, stmts) in mem::take(&mut self.decorations) {
792+
self.ctx.statement_injector.insert_many_after(&address, stmts);
793+
}
794+
}
795+
783796
/// Converts a vec of [`Decorator`] to [`Expression::ArrayExpression`].
784797
fn convert_decorators_to_array_expression(
785798
decorators_iter: impl Iterator<Item = Decorator<'a>>,

crates/oxc_transformer/src/decorator/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,11 @@ impl<'a> Traverse<'a, TransformState<'a>> for Decorator<'a, '_> {
8282
}
8383
}
8484
}
85+
86+
impl<'a> Decorator<'a, '_> {
87+
pub fn exit_class_at_end(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
88+
if self.options.legacy {
89+
self.legacy_decorator.exit_class_at_end(class, ctx);
90+
}
91+
}
92+
}

crates/oxc_transformer/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> {
300300
typescript.exit_class(class, ctx);
301301
}
302302
self.x2_es2022.exit_class(class, ctx);
303+
// `decorator` has some statements should be inserted after `class-properties` plugin.
304+
self.decorator.exit_class_at_end(class, ctx);
303305
}
304306

305307
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {

napi/transform/test/transform.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -403,18 +403,18 @@ describe('typescript', () => {
403403
},
404404
});
405405
expect(ret.code).toMatchInlineSnapshot(`
406-
"import _decorate from "@oxc-project/runtime/helpers/decorate";
407-
class Foo {
408-
constructor() {
409-
this.b = 1;
410-
}
411-
}
412-
_decorate([dec], Foo.prototype, "c", void 0);
413-
class StaticFoo {}
414-
_decorate([dec], StaticFoo, "c", void 0);
415-
StaticFoo.b = 1;
416-
"
417-
`);
406+
"import _decorate from "@oxc-project/runtime/helpers/decorate";
407+
class Foo {
408+
constructor() {
409+
this.b = 1;
410+
}
411+
}
412+
_decorate([dec], Foo.prototype, "c", void 0);
413+
class StaticFoo {}
414+
StaticFoo.b = 1;
415+
_decorate([dec], StaticFoo, "c", void 0);
416+
"
417+
`);
418418
});
419419
});
420420
});

0 commit comments

Comments
 (0)