Skip to content

Commit b3ec73b

Browse files
committed
fix(transformer): handle declare fields in class properties transformation
Fixes issue where declare fields were incorrectly transformed instead of being removed, causing inheritance issues where child declare fields would interfere with parent class property initialization. - Add logic to remove declare fields when set_public_class_fields and remove_class_fields_without_initializer are both true - Add comprehensive test cases covering declare fields in various scenarios - Handle both instance and static declare fields - Support declare fields with computed keys Fixes #13733
1 parent 50e6e3c commit b3ec73b

File tree

9 files changed

+106
-6
lines changed

9 files changed

+106
-6
lines changed

crates/oxc_transformer/src/es2022/class_properties/class.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ impl<'a> ClassProperties<'a, '_> {
261261
#[expect(clippy::match_same_arms)]
262262
match element {
263263
ClassElement::PropertyDefinition(prop) => {
264-
if !prop.r#static {
264+
if !prop.r#static && !prop.declare {
265265
self.convert_instance_property(prop, &mut instance_inits, ctx);
266266
}
267267
}
@@ -766,6 +766,11 @@ impl<'a> ClassProperties<'a, '_> {
766766
class.body.body.retain_mut(|element| {
767767
match element {
768768
ClassElement::PropertyDefinition(prop) => {
769+
if prop.declare {
770+
// Remove `declare` properties
771+
return false;
772+
}
773+
769774
if prop.r#static {
770775
self.convert_static_property(prop, ctx);
771776
} else if prop.computed {

crates/oxc_transformer/src/es2022/class_properties/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for ClassProperties<'a, '_> {
404404
prop: &mut PropertyDefinition<'a>,
405405
_ctx: &mut TraverseCtx<'a>,
406406
) {
407-
if prop.r#static {
407+
if prop.r#static && !prop.declare {
408408
self.flag_entering_static_property_or_block();
409409
}
410410
}
@@ -414,7 +414,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for ClassProperties<'a, '_> {
414414
prop: &mut PropertyDefinition<'a>,
415415
_ctx: &mut TraverseCtx<'a>,
416416
) {
417-
if prop.r#static {
417+
if prop.r#static && !prop.declare {
418418
self.flag_exiting_static_property_or_block();
419419
}
420420
}

tasks/transform_conformance/snapshots/oxc.snap.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: 41d96516
22

3-
Passed: 186/313
3+
Passed: 187/315
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -39,7 +39,7 @@ after transform: SymbolId(4): ScopeId(1)
3939
rebuilt : SymbolId(5): ScopeId(4)
4040

4141

42-
# babel-plugin-transform-class-properties (23/29)
42+
# babel-plugin-transform-class-properties (24/31)
4343
* private-field-resolve-to-method/input.js
4444
x Output mismatch
4545

@@ -52,6 +52,11 @@ x Output mismatch
5252
* static-super-tagged-template/input.js
5353
x Output mismatch
5454

55+
* typescript/declare-computed-keys/input.ts
56+
Symbol reference IDs mismatch for "KEY1":
57+
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2)]
58+
rebuilt : SymbolId(1): []
59+
5560
* typescript/optional-call/input.ts
5661
Symbol reference IDs mismatch for "X":
5762
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(11), ReferenceId(16)]

tasks/transform_conformance/snapshots/oxc_exec.snap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ commit: 41d96516
22

33
node: v22.14.0
44

5-
Passed: 8 of 10 (80.00%)
5+
Passed: 9 of 11 (81.82%)
66

77
Failures:
88

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Test declare fields with computed keys
2+
const KEY1 = "dynamicKey1";
3+
const KEY2 = "dynamicKey2";
4+
5+
class TestClass {
6+
// declare field with computed key should be removed
7+
declare [KEY1]: string;
8+
9+
// regular field with computed key should be transformed
10+
[KEY2]: number = 42;
11+
12+
// static declare field with computed key should be removed
13+
declare static [KEY1 + "Static"]: boolean;
14+
15+
// regular static field with computed key should be transformed
16+
static [KEY2 + "Static"]: string = "static";
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
let _ref;
2+
// Test declare fields with computed keys
3+
const KEY1 = "dynamicKey1";
4+
const KEY2 = "dynamicKey2";
5+
_ref = KEY2 + "Static";
6+
7+
class TestClass {
8+
// regular field with computed key should be transformed
9+
constructor() {
10+
babelHelpers.defineProperty(this, KEY2, 42);
11+
}
12+
13+
// regular static field with computed key should be transformed
14+
}
15+
16+
babelHelpers.defineProperty(TestClass, _ref, "static");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Test case for declare fields issue #13733
2+
class B {
3+
public value: number = 3;
4+
5+
constructor(value?: number) {
6+
if (value !== undefined) {
7+
this.value = value;
8+
}
9+
}
10+
}
11+
12+
class C extends B {
13+
declare public value: number;
14+
15+
log() {
16+
return "C " + this.value
17+
}
18+
}
19+
20+
// This should log "C 6", not "C undefined"
21+
expect(new C(6).log()).toBe("C 6");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Test case for declare fields issue #13733
2+
class B {
3+
public value: number = 3;
4+
5+
constructor(value?: number) {
6+
if (value !== undefined) {
7+
this.value = value;
8+
}
9+
}
10+
}
11+
12+
class C extends B {
13+
public declare value: number;
14+
15+
log() {
16+
return "C " + this.value;
17+
}
18+
}
19+
20+
// This should log "C 6", not "C undefined"
21+
expect(new C(6).log()).toBe("C 6");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
class B {
3+
constructor(value) {
4+
babelHelpers.defineProperty(this, "value", 3);
5+
if (value !== undefined) {
6+
this.value = value;
7+
}
8+
}
9+
}
10+
class C extends B {
11+
log() {
12+
return "C " + this.value;
13+
}
14+
}
15+
expect(new C(6).log()).toBe("C 6");

0 commit comments

Comments
 (0)