mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer/class-properties): use correct property name when converting parameter properties (#21268)
## Summary Fixes #21116 where TypeScript constructor parameter properties emit the wrong property name when the parameter name clashes with a free variable referenced in a class field initializer. ## Problem When targeting pre-ES2022 (or with `transform-class-properties` enabled), the class properties plugin detects that a constructor parameter name shadows an outer variable referenced in a field initializer, and renames the parameter binding (e.g. `x` → `_x`) to preserve the initializer's semantics. `convert_constructor_params` (TypeScript plugin) ran after this rename and read `id.name` — which had already been mutated — to produce the `this.X = X` assignment. This caused: ```ts let x = 10; class Foo { field = x; constructor(public x: number) {} } ``` to incorrectly emit: ```js constructor(_x) { this._x = _x; // ❌ property name should be x ... } ``` ## Root Cause Both plugins share a single traversal pass with interleaved hooks. `enter_class_body` (class properties) fires before `enter_method_definition` (TypeScript), so by the time `convert_constructor_params` runs, `BindingIdentifier.name` has already been mutated by `ConstructorSymbolRenamer`. ## Fix Recover the property name from `id.span.source_text(source_text)` instead of `id.name`. Spans are set by the parser and never mutated by any transformer, so this always returns the original user-written identifier regardless of renames. ```ts constructor(_x) { this.x = _x; // ✅ ... } ``` ## AI Disclosure This fix was developed with Claude Code assistance. Co-authored-by: Dunqing <dengqing0821@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
5 files changed
+29
-3
No files matched your search
@@ -2,6 +2,7 @@ use oxc_allocator::{TakeIn, Vec as ArenaVec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_semantic::ScopeFlags;
|
||||
use oxc_span::SPAN;
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::operator::AssignmentOperator;
|
||||
use oxc_traverse::BoundIdentifier;
|
||||
|
||||
@@ -381,12 +382,15 @@ impl<'a> TypeScript<'a> {
|
||||
params: &ArenaVec<'a, FormalParameter<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> impl Iterator<Item = Statement<'a>> {
|
||||
let source_text = ctx.state.source_text;
|
||||
params
|
||||
.iter()
|
||||
.filter(|param| param.has_modifier())
|
||||
.filter_map(|param| param.pattern.get_binding_identifier())
|
||||
.map(|id| {
|
||||
let target = create_this_property_assignment(id.span, id.name, ctx);
|
||||
// `id.name` may be renamed by clash detection; use span to recover the original source name.
|
||||
let prop_name = Ident::from(id.span.source_text(source_text));
|
||||
let target = create_this_property_assignment(id.span, prop_name, ctx);
|
||||
let value = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx);
|
||||
Self::create_assignment(target, value, ctx)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 91b4ce32
|
||||
|
||||
Passed: 210/347
|
||||
Passed: 211/348
|
||||
|
||||
# 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 (7/31)
|
||||
# babel-plugin-transform-typescript (8/32)
|
||||
* allow-declare-fields-false/input.ts
|
||||
Unresolved references mismatch:
|
||||
after transform: ["dce"]
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// A free reference in a field initializer resolves to an outer variable with the
|
||||
// same name as a constructor parameter property. The constructor parameter gets
|
||||
// renamed to avoid shadowing the outer variable once the initializer moves into
|
||||
// the constructor body. `this.x` must still use the original source name, not `_x`.
|
||||
let x = 10;
|
||||
class Foo {
|
||||
field = x;
|
||||
constructor(public x: number) {}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-class-properties",
|
||||
"transform-typescript"
|
||||
]
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
let x = 10;
|
||||
class Foo {
|
||||
constructor(_x) {
|
||||
this.x = _x;
|
||||
babelHelpers.defineProperty(this, "field", x);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user