mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer/typescript): preserve computed-key static block when class has an empty constructor (#21562)
## Summary
When a TypeScript class used a computed-key static property together with a constructor that has no parameter-property assignments, the early return in `transform_class_fields` dropped the `computed_key_assignment_static_block` that initializes the temp var, leaving it declared but never assigned.
```ts
export class SampleClass {
static [Symbol.toPrimitive] = "test"
constructor() {}
}
```
previously emitted:
```js
let _Symbol$toPrimitive;
export class SampleClass {
static {
this[_Symbol$toPrimitive] = "test";
}
constructor() {}
}
```
Now emits (matching TypeScript's own output under `useDefineForClassFields: false`):
```js
let _Symbol$toPrimitive;
export class SampleClass {
static {
_Symbol$toPrimitive = Symbol.toPrimitive;
}
static {
this[_Symbol$toPrimitive] = "test";
}
constructor() {}
}
```
## Test plan
- Added fixtures `computed-static-property-with-constructor` (exact repro) and `computed-static-property-with-constructor-super` (super() + multiple computed keys) under `babel-plugin-transform-typescript`.
- Verified output against `tsc` 5.7 with `useDefineForClassFields: false`.
Closes rolldown/rolldown#9121
This commit is contained in:
1 parent
50e9d26714
commit
91e5bde420
8 files changed
+78
-38
No files matched your search
@@ -1,6 +1,6 @@
|
||||
use oxc_allocator::{TakeIn, Vec as ArenaVec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_semantic::ScopeFlags;
|
||||
use oxc_semantic::{ScopeFlags, ScopeId};
|
||||
use oxc_span::SPAN;
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::operator::AssignmentOperator;
|
||||
@@ -100,6 +100,10 @@ impl<'a> TypeScript<'a> {
|
||||
)
|
||||
});
|
||||
|
||||
// Static blocks created below must be parented to the class body scope,
|
||||
// not the current traversal scope (which is the scope enclosing the class).
|
||||
let class_scope_id = class.scope_id();
|
||||
|
||||
let mut constructor = None;
|
||||
let mut property_assignments = Vec::new();
|
||||
let mut computed_key_assignments = Vec::new();
|
||||
@@ -123,7 +127,7 @@ impl<'a> TypeScript<'a> {
|
||||
// `class C { static x = 1; }` -> `class C { static { this.x = 1; } }`
|
||||
// `class C { static [x] = 1; }` -> `let _x; class C { static { this[_x] = 1; } }`
|
||||
let body = ctx.ast.vec1(assignment);
|
||||
*element = Self::create_class_static_block(body, ctx);
|
||||
*element = Self::create_class_static_block(body, class_scope_id, ctx);
|
||||
} else {
|
||||
property_assignments.push(assignment);
|
||||
}
|
||||
@@ -170,7 +174,7 @@ impl<'a> TypeScript<'a> {
|
||||
.ast
|
||||
.expression_sequence(SPAN, ctx.ast.vec_from_iter(computed_key_assignments));
|
||||
let statement = ctx.ast.statement_expression(SPAN, sequence_expression);
|
||||
Self::create_class_static_block(ctx.ast.vec1(statement), ctx)
|
||||
Self::create_class_static_block(ctx.ast.vec1(statement), class_scope_id, ctx)
|
||||
});
|
||||
|
||||
if let Some(constructor) = constructor {
|
||||
@@ -179,8 +183,13 @@ impl<'a> TypeScript<'a> {
|
||||
let params_assignment = Self::convert_constructor_params(params, ctx);
|
||||
property_assignments.splice(0..0, params_assignment);
|
||||
|
||||
// Exit if there are no property and parameter assignments
|
||||
if property_assignments.is_empty() {
|
||||
// No property/parameter assignments to inject, but computed-key temp
|
||||
// inits still need their static block (e.g. `static [expr] = value` +
|
||||
// empty `constructor()`), otherwise the temp var is left uninitialized.
|
||||
if let Some(element) = computed_key_assignment_static_block {
|
||||
class.body.body.insert(0, element);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,21 +438,22 @@ impl<'a> TypeScript<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Create `static { body }`
|
||||
/// Create `static { body }` as a child of `class_scope_id`.
|
||||
///
|
||||
/// The enclosing traversal scope is the scope around the class, not the class body,
|
||||
/// so pass `class.scope_id()` explicitly to parent the static block correctly.
|
||||
#[inline]
|
||||
fn create_class_static_block(
|
||||
body: ArenaVec<'a, Statement<'a>>,
|
||||
class_scope_id: ScopeId,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> ClassElement<'a> {
|
||||
let scope_id = ctx.insert_scope_below_statements(
|
||||
&body,
|
||||
let scope_id = ctx.insert_scope_below_statement_from_scope_id(
|
||||
&body[0],
|
||||
class_scope_id,
|
||||
ScopeFlags::StrictMode | ScopeFlags::ClassStaticBlock,
|
||||
);
|
||||
|
||||
ctx.ast.class_element_static_block_with_scope_id(
|
||||
SPAN,
|
||||
ctx.ast.vec_from_iter(body),
|
||||
scope_id,
|
||||
)
|
||||
ctx.ast.class_element_static_block_with_scope_id(SPAN, body, scope_id)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: c543b031
|
||||
|
||||
Passed: 220/367
|
||||
Passed: 222/369
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -69,7 +69,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
|
||||
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
|
||||
|
||||
|
||||
# babel-plugin-transform-typescript (15/48)
|
||||
# babel-plugin-transform-typescript (17/50)
|
||||
* allow-declare-fields-false/input.ts
|
||||
Unresolved references mismatch:
|
||||
after transform: ["dce"]
|
||||
@@ -614,32 +614,8 @@ Scope parent mismatch:
|
||||
after transform: ScopeId(12): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(2): Some(ScopeId(1))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(11): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(3): Some(ScopeId(1))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(13): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(5): Some(ScopeId(4))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(16): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(8): Some(ScopeId(7))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(14): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(9): Some(ScopeId(7))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(15): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(10): Some(ScopeId(7))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(17): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(12): Some(ScopeId(11))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(18): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(17): Some(ScopeId(15))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(19): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(19): Some(ScopeId(15))
|
||||
Scope parent mismatch:
|
||||
after transform: ScopeId(20): Some(ScopeId(0))
|
||||
rebuilt : ScopeId(20): Some(ScopeId(15))
|
||||
Unresolved reference IDs mismatch for "dce":
|
||||
after transform: [ReferenceId(0), ReferenceId(1), ReferenceId(4), ReferenceId(9), ReferenceId(12), ReferenceId(14), ReferenceId(17)]
|
||||
rebuilt : [ReferenceId(5)]
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
class Sub extends Base {
|
||||
static [x()] = 1;
|
||||
static [y()] = 2;
|
||||
|
||||
constructor(a: number) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"assumptions": {
|
||||
"setPublicClassFields": true
|
||||
},
|
||||
"plugins": [
|
||||
["transform-typescript"]
|
||||
]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
let _x, _y;
|
||||
class Sub extends Base {
|
||||
static {
|
||||
_x = x(), _y = y();
|
||||
}
|
||||
static {
|
||||
this[_x] = 1;
|
||||
}
|
||||
static {
|
||||
this[_y] = 2;
|
||||
}
|
||||
constructor(a) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export class SampleClass {
|
||||
static [Symbol.toPrimitive] = "test";
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"assumptions": {
|
||||
"setPublicClassFields": true
|
||||
},
|
||||
"plugins": [
|
||||
["transform-typescript"]
|
||||
]
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
let _Symbol$toPrimitive;
|
||||
export class SampleClass {
|
||||
static {
|
||||
_Symbol$toPrimitive = Symbol.toPrimitive;
|
||||
}
|
||||
static {
|
||||
this[_Symbol$toPrimitive] = "test";
|
||||
}
|
||||
constructor() {}
|
||||
}
|
||||
Reference in new issue
Block a user