mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer/decorators): avoid accessor storage name collisions (#21106)
## Summary - Use raw property/identifier names instead of `get_var_name_from_node` (which strips leading underscores) when generating private backing field names for accessor properties - For non-computed keys, use `PropertyKey::name()` which preserves leading underscores - For computed keys, match on `Expression::Identifier` to get the raw name directly - No counters, maps, or helper functions needed — collisions are avoided at the source This prevents `accessor prop` and `accessor _prop` from producing the same `#_prop_accessor_storage` name, which caused a runtime `TypeError: Cannot initialize the same private elements twice on an object`. Closes #20946 Supersedes #21023 ## Test plan - [x] Added test fixture covering both non-computed (`prop`/`_prop`) and computed (`[prop]`/`[_prop]`) accessor name collisions - [x] All existing accessor tests pass unchanged - [x] `cargo test -p oxc_transformer` passes - [x] Full `cargo run -p oxc_transform_conformance` passes - [x] `cargo clippy` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
1 parent
0d678345aa
commit
eab13b39e3
6 files changed
+95
-37
No files matched your search
@@ -45,6 +45,7 @@
|
||||
|
||||
mod metadata;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::mem;
|
||||
|
||||
use oxc_allocator::{Address, CloneIn, GetAddress, TakeIn, UnstableAddress, Vec as ArenaVec};
|
||||
@@ -369,20 +370,26 @@ impl<'a> LegacyDecorator<'a> {
|
||||
let key_expr = accessor.key.into_expression();
|
||||
let (assignment, reference) = duplicate_expression(key_expr, true, ctx);
|
||||
|
||||
// Use raw identifier name when possible to avoid collisions from
|
||||
// `get_var_name_from_node` stripping leading underscores.
|
||||
let key_name: Cow<'_, str> = match &reference {
|
||||
Expression::Identifier(ident) => Cow::Borrowed(ident.name.as_str()),
|
||||
_ => Cow::Owned(get_var_name_from_node(&reference)),
|
||||
};
|
||||
let getter_key = PropertyKey::from(assignment);
|
||||
let setter_key = PropertyKey::from(reference);
|
||||
let storage_name = ctx.ast.str_from_strs_array([
|
||||
"_",
|
||||
&get_var_name_from_node(&setter_key),
|
||||
"_computed_accessor_storage",
|
||||
]);
|
||||
let storage_name =
|
||||
ctx.ast.str_from_strs_array(["_", &key_name, "_computed_accessor_storage"]);
|
||||
(storage_name, getter_key, setter_key)
|
||||
} else {
|
||||
let storage_name = ctx.ast.str_from_strs_array([
|
||||
"_",
|
||||
&get_var_name_from_node(&accessor.key),
|
||||
"_accessor_storage",
|
||||
]);
|
||||
// Use `name()` to get the raw property name, avoiding `get_var_name_from_node`
|
||||
// which strips leading underscores (e.g. `prop` and `_prop` both become "prop").
|
||||
let key_name = accessor
|
||||
.key
|
||||
.name()
|
||||
.unwrap_or_else(|| Cow::Owned(get_var_name_from_node(&accessor.key)));
|
||||
let storage_name =
|
||||
ctx.ast.str_from_strs_array(["_", &key_name, "_accessor_storage"]);
|
||||
let getter_key = accessor.key.clone_in(ctx.ast.allocator);
|
||||
let setter_key = accessor.key.clone_in(ctx.ast.allocator);
|
||||
(storage_name, getter_key, setter_key)
|
||||
|
||||
@@ -19823,8 +19823,8 @@ rebuilt : ScopeId(0): ["C1", "_a_accessor_storage", "_b_accessor_storage"
|
||||
|
||||
semantic Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/autoAccessorAllowedModifiers.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["C1", "C2", "C3", "_08_accessor_storage", "_C1_brand", "_a_accessor_storage", "_b_accessor_storage", "_c_accessor_storage", "_classPrivateFieldGet", "_classPrivateFieldInitSpec", "_classPrivateFieldSet", "_classPrivateMethodInitSpec", "_d_accessor_storage", "_e_accessor_storage", "_f_accessor_storage", "_g_accessor_storage", "_get_j", "_h_accessor_storage", "_i_accessor_storage", "_i_accessor_storage2", "_j_accessor_storage", "_k_accessor_storage", "_m_computed_accessor_storage", "_n_accessor_storage", "_set_j"]
|
||||
rebuilt : ScopeId(0): ["C1", "C2", "_08_accessor_storage", "_C1_brand", "_a_accessor_storage", "_b_accessor_storage", "_c_accessor_storage", "_classPrivateFieldGet", "_classPrivateFieldInitSpec", "_classPrivateFieldSet", "_classPrivateMethodInitSpec", "_d_accessor_storage", "_e_accessor_storage", "_f_accessor_storage", "_g_accessor_storage", "_get_j", "_h_accessor_storage", "_i_accessor_storage", "_i_accessor_storage2", "_j_accessor_storage", "_k_accessor_storage", "_m_computed_accessor_storage", "_n_accessor_storage", "_set_j"]
|
||||
after transform: ScopeId(0): ["C1", "C2", "C3", "_108_accessor_storage", "_C1_brand", "_a_accessor_storage", "_b_accessor_storage", "_c_accessor_storage", "_classPrivateFieldGet", "_classPrivateFieldInitSpec", "_classPrivateFieldSet", "_classPrivateMethodInitSpec", "_d_accessor_storage", "_e_accessor_storage", "_f_accessor_storage", "_g_accessor_storage", "_get_j", "_h_accessor_storage", "_i_accessor_storage", "_i_accessor_storage2", "_j_accessor_storage", "_k_accessor_storage", "_m_computed_accessor_storage", "_n_accessor_storage", "_set_j"]
|
||||
rebuilt : ScopeId(0): ["C1", "C2", "_108_accessor_storage", "_C1_brand", "_a_accessor_storage", "_b_accessor_storage", "_c_accessor_storage", "_classPrivateFieldGet", "_classPrivateFieldInitSpec", "_classPrivateFieldSet", "_classPrivateMethodInitSpec", "_d_accessor_storage", "_e_accessor_storage", "_f_accessor_storage", "_g_accessor_storage", "_get_j", "_h_accessor_storage", "_i_accessor_storage", "_i_accessor_storage2", "_j_accessor_storage", "_k_accessor_storage", "_m_computed_accessor_storage", "_n_accessor_storage", "_set_j"]
|
||||
|
||||
semantic Error: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/overrideInterfaceProperty.ts
|
||||
Reference symbol mismatch for "Mup":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 91b4ce32
|
||||
|
||||
Passed: 210/346
|
||||
Passed: 210/347
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -555,38 +555,29 @@ x Output mismatch
|
||||
x Output mismatch
|
||||
|
||||
|
||||
# legacy-decorators (8/93)
|
||||
# legacy-decorators (8/94)
|
||||
* oxc/accessor/input.ts
|
||||
x Output mismatch
|
||||
|
||||
* oxc/accessor-name-collision/input.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["C", "_a", "_foo$bar", "_foo$bar2", "a", "dec", "foo"]
|
||||
rebuilt : ScopeId(0): ["C", "_a", "_foo$bar", "_foo$bar2"]
|
||||
Reference symbol mismatch for "a":
|
||||
after transform: SymbolId(4) "a"
|
||||
after transform: ScopeId(0): ["Foo", "_prop", "_prop2", "_prop3", "prop", "property"]
|
||||
rebuilt : ScopeId(0): ["Foo", "_prop", "_prop2", "_prop3", "prop"]
|
||||
Reference symbol mismatch for "property":
|
||||
after transform: SymbolId(0) "property"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "a":
|
||||
after transform: SymbolId(4) "a"
|
||||
Reference symbol mismatch for "property":
|
||||
after transform: SymbolId(0) "property"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "foo":
|
||||
after transform: SymbolId(5) "foo"
|
||||
Reference symbol mismatch for "property":
|
||||
after transform: SymbolId(0) "property"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "dec":
|
||||
after transform: SymbolId(0) "dec"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "dec":
|
||||
after transform: SymbolId(0) "dec"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "dec":
|
||||
after transform: SymbolId(0) "dec"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "dec":
|
||||
after transform: SymbolId(0) "dec"
|
||||
rebuilt : <None>
|
||||
Reference symbol mismatch for "dec":
|
||||
after transform: SymbolId(0) "dec"
|
||||
Reference symbol mismatch for "property":
|
||||
after transform: SymbolId(0) "property"
|
||||
rebuilt : <None>
|
||||
Unresolved references mismatch:
|
||||
after transform: ["PropertyDescriptor", "babelHelpers"]
|
||||
rebuilt : ["a", "babelHelpers", "dec", "foo"]
|
||||
rebuilt : ["babelHelpers", "property"]
|
||||
|
||||
* oxc/accessor-with-class-properties/input.ts
|
||||
Bindings mismatch:
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
declare function property(): (target: any, propertyKey: string, desc: PropertyDescriptor) => void;
|
||||
|
||||
const prop = 'foo'
|
||||
const _prop = 'bar'
|
||||
|
||||
export class Foo {
|
||||
@property()
|
||||
accessor prop!: string;
|
||||
|
||||
@property()
|
||||
accessor _prop!: string;
|
||||
|
||||
@property()
|
||||
accessor [prop]!: string;
|
||||
|
||||
@property()
|
||||
accessor [_prop]!: string;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-legacy-decorator"
|
||||
]
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var _prop2, _prop3;
|
||||
const prop = "foo";
|
||||
const _prop = "bar";
|
||||
export class Foo {
|
||||
#_prop_accessor_storage;
|
||||
get prop() {
|
||||
return this.#_prop_accessor_storage;
|
||||
}
|
||||
set prop(value) {
|
||||
this.#_prop_accessor_storage = value;
|
||||
}
|
||||
#__prop_accessor_storage;
|
||||
get _prop() {
|
||||
return this.#__prop_accessor_storage;
|
||||
}
|
||||
set _prop(value) {
|
||||
this.#__prop_accessor_storage = value;
|
||||
}
|
||||
#_prop_computed_accessor_storage;
|
||||
get [_prop2 = prop]() {
|
||||
return this.#_prop_computed_accessor_storage;
|
||||
}
|
||||
set [prop](value) {
|
||||
this.#_prop_computed_accessor_storage = value;
|
||||
}
|
||||
#__prop_computed_accessor_storage;
|
||||
get [_prop3 = _prop]() {
|
||||
return this.#__prop_computed_accessor_storage;
|
||||
}
|
||||
set [_prop](value) {
|
||||
this.#__prop_computed_accessor_storage = value;
|
||||
}
|
||||
}
|
||||
babelHelpers.decorate([property()], Foo.prototype, "prop", null);
|
||||
babelHelpers.decorate([property()], Foo.prototype, "_prop", null);
|
||||
babelHelpers.decorate([property()], Foo.prototype, _prop2, null);
|
||||
babelHelpers.decorate([property()], Foo.prototype, _prop3, null);
|
||||
Reference in new issue
Block a user