Skip to content

Commit 16f8e20

Browse files
committed
refactor(transformer/decorator): eliminate unreliable identification of metadata
1 parent d27a04b commit 16f8e20

File tree

5 files changed

+78
-48
lines changed

5 files changed

+78
-48
lines changed

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

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@
8787
///
8888
/// ## References
8989
/// * TypeScript's [emitDecoratorMetadata](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata)
90+
use std::collections::VecDeque;
91+
9092
use oxc_allocator::{Box as ArenaBox, TakeIn};
9193
use oxc_ast::ast::*;
94+
use oxc_data_structures::stack::NonEmptyStack;
9295
use oxc_semantic::ReferenceFlags;
9396
use oxc_span::{ContentEq, SPAN};
9497
use oxc_traverse::{MaybeBoundIdentifier, Traverse};
@@ -100,13 +103,19 @@ use crate::{
100103
utils::ast_builder::create_property_access,
101104
};
102105

106+
pub enum MethodMetadata<'a> {
107+
Constructor(Expression<'a>),
108+
Normal([Expression<'a>; 3]),
109+
}
110+
103111
pub struct LegacyDecoratorMetadata<'a, 'ctx> {
104112
ctx: &'ctx TransformCtx<'a>,
113+
metadata_stack: NonEmptyStack<VecDeque<MethodMetadata<'a>>>,
105114
}
106115

107116
impl<'a, 'ctx> LegacyDecoratorMetadata<'a, 'ctx> {
108117
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
109-
LegacyDecoratorMetadata { ctx }
118+
LegacyDecoratorMetadata { ctx, metadata_stack: NonEmptyStack::new(VecDeque::new()) }
110119
}
111120
}
112121

@@ -121,19 +130,18 @@ impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecoratorMetadata<'a, '_> {
121130
_ => None,
122131
});
123132

124-
if let Some(constructor) = constructor {
125-
if class.decorators.is_empty()
126-
&& constructor.value.params.items.iter().all(|param| param.decorators.is_empty())
127-
{
128-
return;
129-
}
130-
133+
if let Some(constructor) = constructor
134+
&& !(class.decorators.is_empty()
135+
&& constructor.value.params.items.iter().all(|param| param.decorators.is_empty()))
136+
{
131137
let serialized_type =
132138
self.serialize_parameter_types_of_node(&constructor.value.params, ctx);
133-
let metadata_decorator =
134-
self.create_metadata_decorate("design:paramtypes", serialized_type, ctx);
135139

136-
class.decorators.push(metadata_decorator);
140+
self.metadata_stack.push(VecDeque::from_iter([MethodMetadata::Constructor(
141+
self.create_metadata("design:paramtypes", serialized_type, ctx),
142+
)]));
143+
} else {
144+
self.metadata_stack.push(VecDeque::new());
137145
}
138146
}
139147

@@ -152,18 +160,19 @@ impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecoratorMetadata<'a, '_> {
152160
return;
153161
}
154162

155-
method.decorators.extend([
156-
self.create_metadata_decorate("design:type", Self::global_function(ctx), ctx),
163+
let metadata = [
164+
self.create_metadata("design:type", Self::global_function(ctx), ctx),
157165
{
158166
let serialized_type =
159167
self.serialize_parameter_types_of_node(&method.value.params, ctx);
160-
self.create_metadata_decorate("design:paramtypes", serialized_type, ctx)
168+
self.create_metadata("design:paramtypes", serialized_type, ctx)
161169
},
162170
{
163171
let serialized_type = self.serialize_return_type_of_node(&method.value, ctx);
164-
self.create_metadata_decorate("design:returntype", serialized_type, ctx)
172+
self.create_metadata("design:returntype", serialized_type, ctx)
165173
},
166-
]);
174+
];
175+
self.metadata_stack.last_mut().push_back(MethodMetadata::Normal(metadata));
167176
}
168177

169178
#[inline]
@@ -192,6 +201,10 @@ impl<'a> Traverse<'a, TransformState<'a>> for LegacyDecoratorMetadata<'a, '_> {
192201
}
193202

194203
impl<'a> LegacyDecoratorMetadata<'a, '_> {
204+
pub fn pop_method_metadata(&mut self) -> Option<MethodMetadata<'a>> {
205+
self.metadata_stack.last_mut().pop_front()
206+
}
207+
195208
fn serialize_type_annotation(
196209
&mut self,
197210
type_annotation: Option<&ArenaBox<'a, TSTypeAnnotation<'a>>>,
@@ -607,18 +620,27 @@ impl<'a> LegacyDecoratorMetadata<'a, '_> {
607620
}
608621

609622
// `_metadata(key, value)
610-
fn create_metadata_decorate(
623+
fn create_metadata(
611624
&self,
612625
key: &'a str,
613626
value: Expression<'a>,
614627
ctx: &mut TraverseCtx<'a>,
615-
) -> Decorator<'a> {
628+
) -> Expression<'a> {
616629
let arguments = ctx.ast.vec_from_array([
617630
Argument::from(ctx.ast.expression_string_literal(SPAN, key, None)),
618631
Argument::from(value),
619632
]);
620-
let expr = self.ctx.helper_call_expr(Helper::DecorateMetadata, SPAN, arguments, ctx);
621-
ctx.ast.decorator(SPAN, expr)
633+
self.ctx.helper_call_expr(Helper::DecorateMetadata, SPAN, arguments, ctx)
634+
}
635+
636+
// `_metadata(key, value)
637+
fn create_metadata_decorate(
638+
&self,
639+
key: &'a str,
640+
value: Expression<'a>,
641+
ctx: &mut TraverseCtx<'a>,
642+
) -> Decorator<'a> {
643+
ctx.ast.decorator(SPAN, self.create_metadata(key, value, ctx))
622644
}
623645

624646
/// `_metadata("design:type", type)`

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

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use crate::{
6262
state::TransformState,
6363
utils::ast_builder::{create_assignment, create_prototype_member},
6464
};
65-
use metadata::LegacyDecoratorMetadata;
65+
use metadata::{LegacyDecoratorMetadata, MethodMetadata};
6666

6767
#[derive(Default)]
6868
struct ClassDecoratorInfo {
@@ -598,7 +598,7 @@ impl<'a> LegacyDecorator<'a, '_> {
598598
/// Transform decorators of [`ClassElement::MethodDefinition`],
599599
/// [`ClassElement::PropertyDefinition`] and [`ClassElement::AccessorProperty`].
600600
fn transform_decorators_of_class_elements(
601-
&self,
601+
&mut self,
602602
class: &mut Class<'a>,
603603
class_binding: &BoundIdentifier<'a>,
604604
ctx: &mut TraverseCtx<'a>,
@@ -680,7 +680,7 @@ impl<'a> LegacyDecorator<'a, '_> {
680680
/// ], Class);
681681
/// ```
682682
fn transform_decorators_of_class_and_constructor(
683-
&self,
683+
&mut self,
684684
class: &mut Class<'a>,
685685
class_binding: &BoundIdentifier<'a>,
686686
class_alias_binding: Option<&BoundIdentifier<'a>>,
@@ -821,7 +821,7 @@ impl<'a> LegacyDecorator<'a, '_> {
821821
/// ]
822822
/// ```
823823
fn get_all_decorators_of_class_method(
824-
&self,
824+
&mut self,
825825
method: &mut MethodDefinition<'a>,
826826
ctx: &mut TraverseCtx<'a>,
827827
) -> Option<Expression<'a>> {
@@ -836,24 +836,11 @@ impl<'a> LegacyDecorator<'a, '_> {
836836

837837
let mut decorations = ctx.ast.vec_with_capacity(method_decoration_count);
838838

839-
// Split metadata decorators from method decorators.
840-
// Metadata decorators (typically used for emitting design-time type information)
841-
// are identified by having an "unspanned" span. According to TypeScript's legacy
842-
// decorator semantics, metadata decorators must be applied *after* all parameter
843-
// decorators, so we separate them here and will insert them last.
844-
let mut method_decorators = method.decorators.take_in(ctx.ast);
845-
let metadata_position = method_decorators
846-
.iter()
847-
.position(|decorator| {
848-
// All metadata decorators are unspanned
849-
decorator.span.is_unspanned()
850-
})
851-
.unwrap_or(method_decorators.len());
852-
let metadata_decorators = method_decorators.split_off(metadata_position);
853-
854839
// Method decorators should always be injected before all other decorators
855840
decorations.extend(
856-
method_decorators
841+
method
842+
.decorators
843+
.take_in(ctx.ast)
857844
.into_iter()
858845
.map(|decorator| ArrayExpressionElement::from(decorator.expression)),
859846
);
@@ -864,11 +851,16 @@ impl<'a> LegacyDecorator<'a, '_> {
864851
}
865852

866853
// `decorateMetadata` should always be injected after param decorators
867-
decorations.extend(
868-
metadata_decorators
869-
.into_iter()
870-
.map(|decorator| ArrayExpressionElement::from(decorator.expression)),
871-
);
854+
if let Some(metadata) = self.metadata.pop_method_metadata() {
855+
match metadata {
856+
MethodMetadata::Constructor(meta) => {
857+
decorations.push(ArrayExpressionElement::from(meta));
858+
}
859+
MethodMetadata::Normal(meta) => {
860+
decorations.extend(meta.map(ArrayExpressionElement::from));
861+
}
862+
}
863+
}
872864

873865
Some(ctx.ast.expression_array(SPAN, decorations))
874866
}

tasks/transform_conformance/snapshots/oxc.snap.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,12 @@ rebuilt : ScopeId(0): ["Foo"]
592592
Scope children mismatch:
593593
after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3)]
594594
rebuilt : ScopeId(0): [ScopeId(1)]
595+
Symbol span mismatch for "Foo":
596+
after transform: SymbolId(4): Span { start: 107, end: 110 }
597+
rebuilt : SymbolId(0): Span { start: 0, end: 0 }
598+
Symbol span mismatch for "Foo":
599+
after transform: SymbolId(11): Span { start: 0, end: 0 }
600+
rebuilt : SymbolId(1): Span { start: 107, end: 110 }
595601
Reference symbol mismatch for "methodDecorator":
596602
after transform: SymbolId(0) "methodDecorator"
597603
rebuilt : <None>
@@ -616,9 +622,12 @@ rebuilt : <None>
616622
Reference symbol mismatch for "paramDecorator":
617623
after transform: SymbolId(2) "paramDecorator"
618624
rebuilt : <None>
625+
Reference symbol mismatch for "paramDecorator":
626+
after transform: SymbolId(2) "paramDecorator"
627+
rebuilt : <None>
619628
Unresolved references mismatch:
620-
after transform: ["Boolean", "Function", "String", "babelHelpers"]
621-
rebuilt : ["Boolean", "Function", "String", "babelHelpers", "methodDecorator", "paramDecorator"]
629+
after transform: ["Boolean", "Function", "Number", "String", "babelHelpers"]
630+
rebuilt : ["Boolean", "Function", "Number", "String", "babelHelpers", "methodDecorator", "paramDecorator"]
622631

623632
* oxc/metadata/this/input.ts
624633
Symbol span mismatch for "Example":

tasks/transform_conformance/tests/legacy-decorators/test/fixtures/oxc/metadata/params/input.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class Foo {
1414
return !!param
1515
}
1616

17+
constructor(@paramDecorator param: number) {
18+
19+
}
20+
1721
method3(@paramDecorator param: string): boolean {
1822
return !!param
1923
}

tasks/transform_conformance/tests/legacy-decorators/test/fixtures/oxc/metadata/params/output.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
export class Foo {
1+
let Foo = class Foo {
22
method1(param) {
33
return !!param;
44
}
55
method2(param) {
66
return !!param;
77
}
8+
constructor(param) {}
89
method3(param) {
910
return !!param;
1011
}
@@ -64,3 +65,5 @@ babelHelpers.decorate(
6465
"method4",
6566
null,
6667
);
68+
Foo = babelHelpers.decorate([babelHelpers.decorateParam(0, paramDecorator), babelHelpers.decorateMetadata("design:paramtypes", [Number])], Foo);
69+
export { Foo };

0 commit comments

Comments
 (0)