Skip to content

Commit 211e9a7

Browse files
committed
feat(transformer/class-properties): transform static/instance accessor methods
1 parent bf6fa4d commit 211e9a7

File tree

27 files changed

+1059
-385
lines changed

27 files changed

+1059
-385
lines changed

crates/oxc_transformer/src/common/helper_loader.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ pub enum Helper {
161161
ClassPrivateFieldLooseBase,
162162
SuperPropGet,
163163
SuperPropSet,
164+
ReadOnlyError,
165+
WriteOnlyError,
164166
}
165167

166168
impl Helper {
@@ -187,6 +189,8 @@ impl Helper {
187189
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
188190
Self::SuperPropGet => "superPropGet",
189191
Self::SuperPropSet => "superPropSet",
192+
Self::ReadOnlyError => "readOnlyError",
193+
Self::WriteOnlyError => "writeOnlyError",
190194
}
191195
}
192196
}

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

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! ES2022: Class Properties
22
//! Transform of class itself.
33
4+
use indexmap::map::Entry;
45
use oxc_allocator::{Address, GetAddress};
56
use oxc_ast::{ast::*, NONE};
67
use oxc_span::SPAN;
@@ -93,7 +94,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
9394
let binding = ctx.generate_uid_in_current_hoist_scope(&ident.name);
9495
private_props.insert(
9596
ident.name.clone(),
96-
PrivateProp::new(binding, prop.r#static, false, false),
97+
PrivateProp::new(binding, prop.r#static, None, false),
9798
);
9899
}
99100

@@ -135,10 +136,22 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
135136
ctx.current_block_scope_id(),
136137
SymbolFlags::FunctionScopedVariable,
137138
);
138-
private_props.insert(
139-
ident.name.clone(),
140-
PrivateProp::new(binding, method.r#static, true, false),
141-
);
139+
140+
match private_props.entry(ident.name.clone()) {
141+
Entry::Occupied(mut entry) => {
142+
// If there's already a binding for this private property,
143+
// it's a setter or getter, so store the binding in `binding2`.
144+
entry.get_mut().set_binding2(binding);
145+
}
146+
Entry::Vacant(entry) => {
147+
entry.insert(PrivateProp::new(
148+
binding,
149+
method.r#static,
150+
Some(method.kind),
151+
false,
152+
));
153+
}
154+
}
142155
}
143156
}
144157
ClassElement::AccessorProperty(prop) => {
@@ -148,7 +161,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
148161
let dummy_binding = BoundIdentifier::new(Atom::empty(), SymbolId::new(0));
149162
private_props.insert(
150163
ident.name.clone(),
151-
PrivateProp::new(dummy_binding, prop.r#static, true, true),
164+
PrivateProp::new(dummy_binding, prop.r#static, None, true),
152165
);
153166
}
154167
}
@@ -443,7 +456,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
443456
self.ctx.statement_injector.insert_many_before(
444457
&stmt_address,
445458
private_props.iter().filter_map(|(name, prop)| {
446-
if prop.is_method || prop.is_accessor {
459+
if prop.is_method() || prop.is_accessor {
447460
return None;
448461
}
449462

@@ -459,10 +472,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
459472
self.ctx.statement_injector.insert_many_before(
460473
&stmt_address,
461474
private_props.values().filter_map(|prop| {
462-
if prop.is_static || (prop.is_method && has_method) || prop.is_accessor {
475+
if prop.is_static || (prop.is_method() && has_method) || prop.is_accessor {
463476
return None;
464477
}
465-
if prop.is_method {
478+
if prop.is_method() {
466479
// `var _C_brand = new WeakSet();`
467480
has_method = true;
468481
let binding = class_details.bindings.brand();
@@ -583,7 +596,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
583596
// TODO(improve-on-babel): Simplify this.
584597
if self.private_fields_as_properties {
585598
exprs.extend(private_props.iter().filter_map(|(name, prop)| {
586-
if prop.is_method || prop.is_accessor {
599+
if prop.is_method() || prop.is_accessor {
587600
return None;
588601
}
589602

@@ -598,7 +611,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
598611
let mut weakmap_symbol_id = None;
599612
let mut has_method = false;
600613
exprs.extend(private_props.values().filter_map(|prop| {
601-
if prop.is_method || prop.is_accessor {
614+
if prop.is_method() || prop.is_accessor {
602615
if prop.is_static || has_method {
603616
return None;
604617
}
@@ -703,6 +716,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
703716
/// * Extract computed key assignments and insert them before class.
704717
/// * Remove all properties, private methods and static blocks from class body.
705718
fn transform_class_elements(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
719+
let mut class_methods = vec![];
706720
class.body.body.retain_mut(|element| {
707721
match element {
708722
ClassElement::PropertyDefinition(prop) => {
@@ -721,7 +735,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
721735
}
722736
ClassElement::MethodDefinition(method) => {
723737
self.substitute_temp_var_for_method_computed_key(method, ctx);
724-
if self.convert_private_method(method, ctx) {
738+
if let Some(statement) = self.convert_private_method(method, ctx) {
739+
class_methods.push(statement);
725740
return false;
726741
}
727742
}
@@ -732,6 +747,11 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
732747

733748
true
734749
});
750+
751+
// All methods are moved to after the class, but need to be before static properties
752+
// TODO(improve-on-babel): Insertion order doesn't matter, and it more clear to insert according to
753+
// definition order.
754+
self.insert_after_stmts.splice(0..0, class_methods);
735755
}
736756

737757
/// Flag that static private fields should be transpiled using temp binding,

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

Lines changed: 139 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,33 @@ impl<'a> ClassDetails<'a> {
4040
pub(super) struct PrivateProp<'a> {
4141
pub binding: BoundIdentifier<'a>,
4242
pub is_static: bool,
43-
pub is_method: bool,
43+
pub method_kind: Option<MethodDefinitionKind>,
4444
pub is_accessor: bool,
45+
// For accessor methods, they have two bindings,
46+
// one for getter and another for setter.
47+
pub binding2: Option<BoundIdentifier<'a>>,
4548
}
4649

4750
impl<'a> PrivateProp<'a> {
4851
pub fn new(
4952
binding: BoundIdentifier<'a>,
5053
is_static: bool,
51-
is_method: bool,
54+
method_kind: Option<MethodDefinitionKind>,
5255
is_accessor: bool,
5356
) -> Self {
54-
Self { binding, is_static, is_method, is_accessor }
57+
Self { binding, is_static, method_kind, is_accessor, binding2: None }
58+
}
59+
60+
pub fn is_method(&self) -> bool {
61+
self.method_kind.is_some()
62+
}
63+
64+
pub fn is_accessor(&self) -> bool {
65+
self.is_accessor || self.method_kind.is_some_and(|kind| kind.is_accessor())
66+
}
67+
68+
pub fn set_binding2(&mut self, binding: BoundIdentifier<'a>) {
69+
self.binding2 = Some(binding);
5570
}
5671
}
5772

@@ -101,46 +116,152 @@ impl<'a> ClassesStack<'a> {
101116
self.stack.last_mut()
102117
}
103118

104-
/// Lookup details of private property referred to by `ident`.
105-
pub fn find_private_prop<'b>(
119+
fn lookup_private_prop<
120+
'b,
121+
Ret,
122+
RetFn: Fn(&'b PrivateProp<'a>, &'b mut ClassBindings<'a>, bool) -> Ret,
123+
>(
106124
&'b mut self,
107125
ident: &PrivateIdentifier<'a>,
108-
) -> ResolvedPrivateProp<'a, 'b> {
126+
ret_fn: RetFn,
127+
) -> Ret {
109128
// Check for binding in closest class first, then enclosing classes.
110129
// We skip the first, because this is a `NonEmptyStack` with dummy first entry.
111130
// TODO: Check there are tests for bindings in enclosing classes.
112131
for class in self.stack[1..].iter_mut().rev() {
113132
if let Some(private_props) = &mut class.private_props {
114133
if let Some(prop) = private_props.get(&ident.name) {
115-
return ResolvedPrivateProp {
116-
prop_binding: &prop.binding,
117-
class_bindings: &mut class.bindings,
118-
is_static: prop.is_static,
119-
is_method: prop.is_method,
120-
is_accessor: prop.is_accessor,
121-
is_declaration: class.is_declaration,
122-
};
134+
return ret_fn(prop, &mut class.bindings, class.is_declaration);
123135
}
124136
}
125137
}
126-
127138
unreachable!();
128139
}
140+
141+
/// Lookup details of private property referred to by `ident`.
142+
pub fn find_private_prop<'b>(
143+
&'b mut self,
144+
ident: &PrivateIdentifier<'a>,
145+
) -> ResolvedPrivateProp<'a, 'b> {
146+
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
147+
ResolvedPrivateProp {
148+
prop_binding: &prop.binding,
149+
class_bindings,
150+
is_static: prop.is_static,
151+
is_method: prop.is_method(),
152+
is_accessor: prop.is_accessor(),
153+
is_declaration,
154+
}
155+
})
156+
}
157+
158+
/// Lookup details of readable private property referred to by `ident`.
159+
pub fn find_readable_private_prop<'b>(
160+
&'b mut self,
161+
ident: &PrivateIdentifier<'a>,
162+
) -> Option<ResolvedPrivateProp<'a, 'b>> {
163+
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
164+
let prop_binding = if matches!(prop.method_kind, Some(MethodDefinitionKind::Set)) {
165+
prop.binding2.as_ref()
166+
} else {
167+
Some(&prop.binding)
168+
};
169+
prop_binding.map(|prop_binding| ResolvedPrivateProp {
170+
prop_binding,
171+
class_bindings,
172+
is_static: prop.is_static,
173+
is_method: prop.is_method(),
174+
is_accessor: prop.is_accessor(),
175+
is_declaration,
176+
})
177+
})
178+
}
179+
180+
/// Lookup details of writeable private property referred to by `ident`.
181+
/// Returns `Some` if it refers to a private prop and setter method
182+
pub fn find_writeable_private_prop<'b>(
183+
&'b mut self,
184+
ident: &PrivateIdentifier<'a>,
185+
) -> Option<ResolvedPrivateProp<'a, 'b>> {
186+
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
187+
let prop_binding = if matches!(prop.method_kind, Some(MethodDefinitionKind::Set) | None)
188+
{
189+
Some(&prop.binding)
190+
} else {
191+
prop.binding2.as_ref()
192+
};
193+
prop_binding.map(|prop_binding| ResolvedPrivateProp {
194+
prop_binding,
195+
class_bindings,
196+
is_static: prop.is_static,
197+
is_method: prop.is_method(),
198+
is_accessor: prop.is_accessor(),
199+
is_declaration,
200+
})
201+
})
202+
}
203+
204+
/// Look up details of the private property referred to by ident and it can either be read or written.
205+
pub fn find_get_set_private_prop<'b>(
206+
&'b mut self,
207+
ident: &PrivateIdentifier<'a>,
208+
) -> ResolvedGetSetPrivateProp<'a, 'b> {
209+
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
210+
let (get_binding, set_binding) = match prop.method_kind {
211+
Some(MethodDefinitionKind::Set) => (prop.binding2.as_ref(), Some(&prop.binding)),
212+
Some(_) => (Some(&prop.binding), prop.binding2.as_ref()),
213+
_ => (Some(&prop.binding), Some(&prop.binding)),
214+
};
215+
ResolvedGetSetPrivateProp {
216+
get_binding,
217+
set_binding,
218+
class_bindings,
219+
is_static: prop.is_static,
220+
is_method: prop.is_method(),
221+
is_accessor: prop.is_accessor(),
222+
is_declaration,
223+
}
224+
})
225+
}
129226
}
130227

131228
/// Details of a private property resolved for a private field.
132229
///
133-
/// This is the return value of [`ClassesStack::find_private_prop`].
230+
/// This is the return value of [`ClassesStack::find_private_prop`],
231+
/// [`ClassesStack::find_readable_private_prop`] and
232+
/// [`ClassesStack::find_writeable_private_prop`].
134233
pub(super) struct ResolvedPrivateProp<'a, 'b> {
135234
/// Binding for temp var representing the property
136235
pub prop_binding: &'b BoundIdentifier<'a>,
137236
/// Bindings for class name and temp var for class
138237
pub class_bindings: &'b mut ClassBindings<'a>,
139238
/// `true` if is a static property
140239
pub is_static: bool,
141-
/// `true` if is a private method
240+
/// `true` if is a private method or accessor property
241+
pub is_method: bool,
242+
/// `true` if is a private accessor property or [`PrivateProp::method_kind`] is
243+
/// `Some(MethodDefinitionKind::Get)` or `Some(MethodDefinitionKind::Set)`
244+
pub is_accessor: bool,
245+
/// `true` if class which defines this property is a class declaration
246+
pub is_declaration: bool,
247+
}
248+
249+
/// Details of a private property resolved for a private field.
250+
///
251+
/// This is the return value of [`ClassesStack::find_get_set_private_prop`].
252+
pub(super) struct ResolvedGetSetPrivateProp<'a, 'b> {
253+
/// Binding for temp var representing the property or getter method
254+
pub get_binding: Option<&'b BoundIdentifier<'a>>,
255+
/// Binding for temp var representing the property or setter method
256+
pub set_binding: Option<&'b BoundIdentifier<'a>>,
257+
/// Bindings for class name and temp var for class
258+
pub class_bindings: &'b mut ClassBindings<'a>,
259+
/// `true` if is a static property
260+
pub is_static: bool,
261+
/// `true` if is a private method or accessor property
142262
pub is_method: bool,
143-
/// `true` if is a private accessor property
263+
/// `true` if is a private accessor property or [`PrivateProp::method_kind`] is
264+
/// `Some(MethodDefinitionKind::Get)` or `Some(MethodDefinitionKind::Set)`
144265
pub is_accessor: bool,
145266
/// `true` if class which defines this property is a class declaration
146267
pub is_declaration: bool,

0 commit comments

Comments
 (0)