fix(semantic): clear member write target for computed keys (#22302)

- clear `MemberWriteTarget` before visiting computed member keys
- add semantic snapshot coverage for computed keys versus computed objects

Fixes #22300
This commit is contained in:
camc314 committed 2026-05-11 08:49:18 +00:00
1 parent 6a8852d910
commit a7c3e22707
3 files changed
+89

No files matched your search

+13
View File
@@ -2073,6 +2073,19 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.current_reference_flags = ReferenceFlags::empty();
}
fn visit_computed_member_expression(&mut self, it: &ComputedMemberExpression<'a>) {
let kind = AstKind::ComputedMemberExpression(self.alloc(it));
self.enter_node(kind);
self.visit_span(&it.span);
self.visit_expression(&it.object);
// The key expression is a read context, not part of the property write —
// strip MemberWriteTarget so it doesn't leak onto identifiers inside the key
// (e.g. `key` in `this[key] = 1`, where `this` doesn't consume the flag).
self.current_reference_flags -= ReferenceFlags::MemberWriteTarget;
self.visit_expression(&it.expression);
self.leave_node(kind);
}
fn visit_simple_assignment_target(&mut self, it: &SimpleAssignmentTarget<'a>) {
// Except that the read-write flags has been set in visit_assignment_expression
// and visit_update_expression, this is always a write-only reference here.
@@ -16,3 +16,14 @@ let c = {};
let d = {};
let e = {};
(true ? d : e).foo = 1;
// No MemberWriteTarget: computed property key
let key;
this[key] = 1;
this[key] += 1;
delete this[key];
// MemberWriteTarget: computed object
let f = {};
f[key] = 1;
f[key] += 1;
@@ -97,6 +97,71 @@ input_file: crates/oxc_semantic/tests/fixtures/oxc/js/assignments/member-write-t
"scope_id": 0
}
]
},
{
"flags": "SymbolFlags(BlockScopedVariable)",
"id": 5,
"name": "key",
"node": "VariableDeclarator(key)",
"references": [
{
"flags": "ReferenceFlags(Read)",
"id": 7,
"name": "key",
"node_id": 71,
"scope_id": 0
},
{
"flags": "ReferenceFlags(Read)",
"id": 8,
"name": "key",
"node_id": 77,
"scope_id": 0
},
{
"flags": "ReferenceFlags(Read)",
"id": 9,
"name": "key",
"node_id": 83,
"scope_id": 0
},
{
"flags": "ReferenceFlags(Read)",
"id": 11,
"name": "key",
"node_id": 92,
"scope_id": 0
},
{
"flags": "ReferenceFlags(Read)",
"id": 13,
"name": "key",
"node_id": 98,
"scope_id": 0
}
]
},
{
"flags": "SymbolFlags(BlockScopedVariable)",
"id": 6,
"name": "f",
"node": "VariableDeclarator(f)",
"references": [
{
"flags": "ReferenceFlags(Read | MemberWriteTarget)",
"id": 10,
"name": "f",
"node_id": 91,
"scope_id": 0
},
{
"flags": "ReferenceFlags(Read | MemberWriteTarget)",
"id": 12,
"name": "f",
"node_id": 97,
"scope_id": 0
}
]
}
]
}