Skip to main content

oxc_transformer/es2022/class_properties/
prop_decl.rs

1//! ES2022: Class Properties
2//! Transform of class property declarations (instance or static properties).
3
4use oxc_ast::{NONE, ast::*};
5use oxc_span::{SPAN, Span};
6use oxc_syntax::reference::ReferenceFlags;
7
8use crate::{
9    common::helper_loader::{Helper, helper_call_expr},
10    context::TraverseCtx,
11    utils::ast_builder::create_assignment,
12};
13
14use super::{
15    ClassProperties,
16    utils::{create_underscore_ident_name, create_variable_declaration},
17};
18
19// Instance properties
20impl<'a> ClassProperties<'a> {
21    /// Convert instance property to initialization expression.
22    /// Property `prop = 123;` -> Expression `this.prop = 123` or `_defineProperty(this, "prop", 123)`.
23    pub(super) fn convert_instance_property(
24        &mut self,
25        prop: &mut PropertyDefinition<'a>,
26        instance_inits: &mut Vec<Expression<'a>>,
27        ctx: &mut TraverseCtx<'a>,
28    ) {
29        // Get value
30        let value = prop.value.take();
31
32        let span = prop.span;
33        let init_expr = if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
34            let value = value.unwrap_or_else(|| ctx.ast.void_0(SPAN));
35            self.create_private_instance_init_assignment(ident, value, span, ctx)
36        } else {
37            let value = match value {
38                Some(value) => value,
39                // Do not need to convert property to `assignee.prop = void 0` if no initializer exists when
40                // `set_public_class_fields` and `remove_class_fields_without_initializer`
41                // are both true.
42                // This is to align `TypeScript` with `useDefineForClassFields: false`.
43                None if self.set_public_class_fields
44                    && self.remove_class_fields_without_initializer =>
45                {
46                    return;
47                }
48                None => ctx.ast.void_0(SPAN),
49            };
50
51            // Convert to assignment or `_defineProperty` call, depending on `loose` option
52            let this = ctx.ast.expression_this(SPAN);
53            self.create_init_assignment(prop, value, this, false, ctx)
54        };
55        instance_inits.push(init_expr);
56    }
57
58    /// Create init assignment for private instance prop, to be inserted into class constructor.
59    ///
60    /// Loose: `Object.defineProperty(this, _prop, {writable: true, value: value})`
61    /// Not loose: `_classPrivateFieldInitSpec(this, _prop, value)`
62    fn create_private_instance_init_assignment(
63        &self,
64        ident: &PrivateIdentifier<'a>,
65        value: Expression<'a>,
66        span: Span,
67        ctx: &mut TraverseCtx<'a>,
68    ) -> Expression<'a> {
69        if self.private_fields_as_properties {
70            let this = ctx.ast.expression_this(SPAN);
71            self.create_private_init_assignment_loose(ident, value, this, span, ctx)
72        } else {
73            self.create_private_instance_init_assignment_not_loose(ident, value, span, ctx)
74        }
75    }
76
77    /// `_classPrivateFieldInitSpec(this, _prop, value)`
78    fn create_private_instance_init_assignment_not_loose(
79        &self,
80        ident: &PrivateIdentifier<'a>,
81        value: Expression<'a>,
82        span: Span,
83        ctx: &mut TraverseCtx<'a>,
84    ) -> Expression<'a> {
85        let private_props = self.current_class().private_props.as_ref().unwrap();
86        let prop = &private_props[&ident.name];
87        let arguments = ctx.ast.vec_from_array([
88            Argument::from(ctx.ast.expression_this(SPAN)),
89            Argument::from(prop.binding.create_read_expression(ctx)),
90            Argument::from(value),
91        ]);
92        helper_call_expr(Helper::ClassPrivateFieldInitSpec, span, arguments, ctx)
93    }
94}
95
96// Static properties
97impl<'a> ClassProperties<'a> {
98    /// Convert static property to initialization expression.
99    /// Property `static prop = 123;` -> Expression `C.prop = 123` or `_defineProperty(C, "prop", 123)`.
100    pub(super) fn convert_static_property(
101        &mut self,
102        prop: &mut PropertyDefinition<'a>,
103        ctx: &mut TraverseCtx<'a>,
104    ) {
105        // Get value.
106        // Transform it to replace `this` and references to class name with temp var for class.
107        // Also transform `super`.
108        let value = prop.value.take().map(|mut value| {
109            self.transform_static_initializer(&mut value, ctx);
110            value
111        });
112
113        let span = prop.span;
114        if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
115            let value = value.unwrap_or_else(|| ctx.ast.void_0(SPAN));
116            self.insert_private_static_init_assignment(ident, value, span, ctx);
117        } else {
118            let value = match value {
119                Some(value) => value,
120                // Do not need to convert property to `assignee.prop = void 0` if no initializer exists when
121                // `set_public_class_fields` and `remove_class_fields_without_initializer`
122                // are both true.
123                // This is to align `TypeScript` with `useDefineForClassFields: false`.
124                None if self.set_public_class_fields
125                    && self.remove_class_fields_without_initializer =>
126                {
127                    return self.extract_computed_key(prop, ctx);
128                }
129                None => ctx.ast.void_0(SPAN),
130            };
131
132            // Convert to assignment or `_defineProperty` call, depending on `loose` option
133            let class_details = self.current_class();
134            let class_binding = if class_details.is_declaration {
135                // Class declarations always have a name except `export default class {}`.
136                // For default export, binding is created when static prop found in 1st pass.
137                class_details.bindings.name.as_ref().unwrap()
138            } else {
139                // Binding is created when static prop found in 1st pass.
140                class_details.bindings.temp.as_ref().unwrap()
141            };
142
143            let assignee = class_binding.create_read_expression(ctx);
144            let init_expr = self.create_init_assignment(prop, value, assignee, true, ctx);
145            self.insert_expr_after_class(init_expr, ctx);
146        }
147    }
148
149    /// Insert after class:
150    ///
151    /// Not loose:
152    /// * Class declaration: `var _prop = {_: value};`
153    /// * Class expression: `_prop = {_: value}`
154    ///
155    /// Loose:
156    /// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
157    fn insert_private_static_init_assignment(
158        &mut self,
159        ident: &PrivateIdentifier<'a>,
160        value: Expression<'a>,
161        span: Span,
162        ctx: &mut TraverseCtx<'a>,
163    ) {
164        if self.private_fields_as_properties {
165            self.insert_private_static_init_assignment_loose(ident, value, span, ctx);
166        } else {
167            self.insert_private_static_init_assignment_not_loose(ident, value, ctx);
168        }
169    }
170
171    /// Insert after class:
172    /// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
173    fn insert_private_static_init_assignment_loose(
174        &mut self,
175        ident: &PrivateIdentifier<'a>,
176        value: Expression<'a>,
177        span: Span,
178        ctx: &mut TraverseCtx<'a>,
179    ) {
180        // TODO: This logic appears elsewhere. De-duplicate it.
181        let class_details = self.current_class();
182        let class_binding = if class_details.is_declaration {
183            // Class declarations always have a name except `export default class {}`.
184            // For default export, binding is created when static prop found in 1st pass.
185            class_details.bindings.name.as_ref().unwrap()
186        } else {
187            // Binding is created when static prop found in 1st pass.
188            class_details.bindings.temp.as_ref().unwrap()
189        };
190
191        let assignee = class_binding.create_read_expression(ctx);
192        let assignment =
193            self.create_private_init_assignment_loose(ident, value, assignee, span, ctx);
194        self.insert_expr_after_class(assignment, ctx);
195    }
196
197    /// Insert after class:
198    ///
199    /// * Class declaration: `var _prop = {_: value};`
200    /// * Class expression: `_prop = {_: value}`
201    fn insert_private_static_init_assignment_not_loose(
202        &mut self,
203        ident: &PrivateIdentifier<'a>,
204        value: Expression<'a>,
205        ctx: &mut TraverseCtx<'a>,
206    ) {
207        // `_prop = {_: value}`
208        let property = ctx.ast.object_property_kind_object_property(
209            SPAN,
210            PropertyKind::Init,
211            PropertyKey::StaticIdentifier(ctx.ast.alloc(create_underscore_ident_name(ctx))),
212            value,
213            false,
214            false,
215            false,
216        );
217        let obj = ctx.ast.expression_object(SPAN, ctx.ast.vec1(property));
218
219        // Insert after class
220        let class_details = self.current_class();
221        let private_props = class_details.private_props.as_ref().unwrap();
222        let prop_binding = &private_props[&ident.name].binding;
223
224        if class_details.is_declaration {
225            // `var _prop = {_: value};`
226            let var_decl = create_variable_declaration(prop_binding, obj, ctx);
227            self.insert_after_stmts.push(var_decl);
228        } else {
229            // `_prop = {_: value}`
230            let assignment = create_assignment(prop_binding, obj, SPAN, ctx);
231            self.insert_after_exprs.push(assignment);
232        }
233    }
234}
235
236// Used for both instance and static properties
237impl<'a> ClassProperties<'a> {
238    /// `assignee.prop = value` or `_defineProperty(assignee, "prop", value)`
239    /// `#[inline]` because the caller has been checked `self.set_public_class_fields`.
240    /// After inlining, the two `self.set_public_class_fields` checks may be folded into one.
241    #[inline]
242    fn create_init_assignment(
243        &mut self,
244        prop: &mut PropertyDefinition<'a>,
245        value: Expression<'a>,
246        assignee: Expression<'a>,
247        is_static: bool,
248        ctx: &mut TraverseCtx<'a>,
249    ) -> Expression<'a> {
250        if self.set_public_class_fields {
251            // `assignee.prop = value`
252            self.create_init_assignment_loose(prop, value, assignee, is_static, ctx)
253        } else {
254            // `_defineProperty(assignee, "prop", value)`
255            self.create_init_assignment_not_loose(prop, value, assignee, is_static, ctx)
256        }
257    }
258
259    /// `this.prop = value` or `_Class.prop = value`
260    fn create_init_assignment_loose(
261        &mut self,
262        prop: &mut PropertyDefinition<'a>,
263        value: Expression<'a>,
264        assignee: Expression<'a>,
265        is_static: bool,
266        ctx: &mut TraverseCtx<'a>,
267    ) -> Expression<'a> {
268        // In-built static props `name` and `length` need to be set with `_defineProperty`
269        let needs_define = |name: &str| is_static && (name == "name" || name == "length");
270
271        let left = match &mut prop.key {
272            PropertyKey::StaticIdentifier(ident) => {
273                if needs_define(&ident.name) {
274                    return self
275                        .create_init_assignment_not_loose(prop, value, assignee, is_static, ctx);
276                }
277                ctx.ast.member_expression_static(SPAN, assignee, ident.as_ref().clone(), false)
278            }
279            PropertyKey::StringLiteral(str_lit) if needs_define(&str_lit.value) => {
280                return self
281                    .create_init_assignment_not_loose(prop, value, assignee, is_static, ctx);
282            }
283            key @ match_expression!(PropertyKey) => {
284                let key = key.to_expression_mut();
285                // Note: Key can also be static `StringLiteral` or `NumericLiteral`.
286                // `class C { 'x' = true; 123 = false; }`
287                // No temp var is created for these.
288                // TODO: Any other possible static key types?
289                let key = self.create_computed_key_temp_var_if_required(key, is_static, ctx);
290                ctx.ast.member_expression_computed(SPAN, assignee, key, false)
291            }
292            PropertyKey::PrivateIdentifier(_) => {
293                // Handled in `convert_instance_property` and `convert_static_property`
294                unreachable!();
295            }
296        };
297
298        ctx.ast.expression_assignment(
299            prop.span,
300            AssignmentOperator::Assign,
301            AssignmentTarget::from(left),
302            value,
303        )
304    }
305
306    /// `_defineProperty(this, "prop", value)` or `_defineProperty(_Class, "prop", value)`
307    fn create_init_assignment_not_loose(
308        &mut self,
309        prop: &mut PropertyDefinition<'a>,
310        value: Expression<'a>,
311        assignee: Expression<'a>,
312        is_static: bool,
313        ctx: &mut TraverseCtx<'a>,
314    ) -> Expression<'a> {
315        let key = match &mut prop.key {
316            PropertyKey::StaticIdentifier(ident) => {
317                ctx.ast.expression_string_literal(ident.span, ident.name, None)
318            }
319            key @ match_expression!(PropertyKey) => {
320                let key = key.to_expression_mut();
321                // Note: Key can also be static `StringLiteral` or `NumericLiteral`.
322                // `class C { 'x' = true; 123 = false; }`
323                // No temp var is created for these.
324                // TODO: Any other possible static key types?
325                self.create_computed_key_temp_var_if_required(key, is_static, ctx)
326            }
327            PropertyKey::PrivateIdentifier(_) => {
328                // Handled in `convert_instance_property` and `convert_static_property`
329                unreachable!();
330            }
331        };
332
333        let arguments = ctx.ast.vec_from_array([
334            Argument::from(assignee),
335            Argument::from(key),
336            Argument::from(value),
337        ]);
338        helper_call_expr(Helper::DefineProperty, prop.span, arguments, ctx)
339    }
340
341    /// `Object.defineProperty(<assignee>, _prop, {writable: true, value: value})`
342    fn create_private_init_assignment_loose(
343        &self,
344        ident: &PrivateIdentifier<'a>,
345        value: Expression<'a>,
346        assignee: Expression<'a>,
347        span: Span,
348        ctx: &mut TraverseCtx<'a>,
349    ) -> Expression<'a> {
350        // `Object.defineProperty`
351        let object_name = ctx.ast.ident("Object");
352        let object_symbol_id = ctx.scoping().find_binding(ctx.current_scope_id(), object_name);
353        let object =
354            ctx.create_ident_expr(SPAN, object_name, object_symbol_id, ReferenceFlags::Read);
355        let property = ctx.ast.identifier_name(SPAN, "defineProperty");
356        let callee =
357            Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false));
358
359        // `{writable: true, value: <value>}`
360        let prop_def = ctx.ast.expression_object(
361            SPAN,
362            ctx.ast.vec_from_array([
363                ctx.ast.object_property_kind_object_property(
364                    SPAN,
365                    PropertyKind::Init,
366                    ctx.ast.property_key_static_identifier(SPAN, Str::from("writable")),
367                    ctx.ast.expression_boolean_literal(SPAN, true),
368                    false,
369                    false,
370                    false,
371                ),
372                ctx.ast.object_property_kind_object_property(
373                    SPAN,
374                    PropertyKind::Init,
375                    ctx.ast.property_key_static_identifier(SPAN, Str::from("value")),
376                    value,
377                    false,
378                    false,
379                    false,
380                ),
381            ]),
382        );
383
384        let private_props = self.current_class().private_props.as_ref().unwrap();
385        let prop_binding = &private_props[&ident.name].binding;
386        let arguments = ctx.ast.vec_from_array([
387            Argument::from(assignee),
388            Argument::from(prop_binding.create_read_expression(ctx)),
389            Argument::from(prop_def),
390        ]);
391        ctx.ast.expression_call(span, callee, NONE, arguments, false)
392    }
393}