oxc_transformer/es2022/class_properties/
private_method.rs

1//! ES2022: Class Properties
2//! Transform of private method uses e.g. `this.#method()`.
3
4use oxc_allocator::TakeIn;
5use oxc_ast::ast::*;
6use oxc_ast_visit::{VisitMut, walk_mut};
7use oxc_semantic::ScopeFlags;
8use oxc_span::SPAN;
9use oxc_traverse::TraverseCtx;
10
11use crate::Helper;
12
13use super::{
14    ClassProperties,
15    super_converter::{ClassPropertiesSuperConverter, ClassPropertiesSuperConverterMode},
16};
17
18impl<'a> ClassProperties<'a, '_> {
19    /// Convert method definition where the key is a private identifier and
20    /// insert it after the class.
21    ///
22    /// ```js
23    /// class C {
24    ///    #method() {}
25    ///    set #prop(value) {}
26    ///    get #prop() { return 0; }
27    /// }
28    /// ```
29    ///
30    /// ->
31    ///
32    /// ```js
33    /// class C {}
34    /// function _method() {}
35    /// function _set_prop(value) {}
36    /// function _get_prop() { return 0; }
37    /// ```
38    ///
39    /// Returns `true` if the method was converted.
40    pub(super) fn convert_private_method(
41        &mut self,
42        method: &mut MethodDefinition<'a>,
43        ctx: &mut TraverseCtx<'a>,
44    ) -> Option<Statement<'a>> {
45        let MethodDefinition { key, value, span, kind, r#static, .. } = method;
46        let PropertyKey::PrivateIdentifier(ident) = &key else {
47            return None;
48        };
49
50        let mut function = value.take_in_box(ctx.ast.allocator);
51
52        let resolved_private_prop = if *kind == MethodDefinitionKind::Set {
53            self.classes_stack.find_writeable_private_prop(ident)
54        } else {
55            self.classes_stack.find_readable_private_prop(ident)
56        };
57        let temp_binding = resolved_private_prop.unwrap().prop_binding;
58
59        function.span = *span;
60        function.id = Some(temp_binding.create_binding_identifier(ctx));
61        function.r#type = FunctionType::FunctionDeclaration;
62
63        // Change parent scope of function to current scope id and remove
64        // strict mode flag if parent scope is not strict mode.
65        let scope_id = function.scope_id();
66        let new_parent_id = ctx.current_scope_id();
67        ctx.scoping_mut().change_scope_parent_id(scope_id, Some(new_parent_id));
68        let is_strict_mode = ctx.current_scope_flags().is_strict_mode();
69        let flags = ctx.scoping_mut().scope_flags_mut(scope_id);
70        *flags -= ScopeFlags::GetAccessor | ScopeFlags::SetAccessor;
71        if !is_strict_mode {
72            // TODO: Needs to remove all child scopes' strict mode flag if child scope
73            // is inherited from this scope.
74            *flags -= ScopeFlags::StrictMode;
75        }
76
77        PrivateMethodVisitor::new(*r#static, self, ctx)
78            .visit_function(&mut function, ScopeFlags::Function);
79
80        Some(Statement::FunctionDeclaration(function))
81    }
82
83    // `_classPrivateMethodInitSpec(this, brand)`
84    pub(super) fn create_class_private_method_init_spec(
85        &self,
86        ctx: &mut TraverseCtx<'a>,
87    ) -> Expression<'a> {
88        let brand = self.classes_stack.last().bindings.brand.as_ref().unwrap();
89        let arguments = ctx.ast.vec_from_array([
90            Argument::from(ctx.ast.expression_this(SPAN)),
91            Argument::from(brand.create_read_expression(ctx)),
92        ]);
93        self.ctx.helper_call_expr(Helper::ClassPrivateMethodInitSpec, SPAN, arguments, ctx)
94    }
95}
96
97/// Visitor to transform private methods.
98///
99/// Almost the same as `super::static_block_and_prop_init::StaticVisitor`,
100/// but only does following:
101///
102/// 1. Reference to class name to class temp var.
103/// 2. Transform `super` expressions.
104struct PrivateMethodVisitor<'a, 'ctx, 'v> {
105    super_converter: ClassPropertiesSuperConverter<'a, 'ctx, 'v>,
106    /// `TraverseCtx` object.
107    ctx: &'v mut TraverseCtx<'a>,
108}
109
110impl<'a, 'ctx, 'v> PrivateMethodVisitor<'a, 'ctx, 'v> {
111    fn new(
112        is_static: bool,
113        class_properties: &'v mut ClassProperties<'a, 'ctx>,
114        ctx: &'v mut TraverseCtx<'a>,
115    ) -> Self {
116        let mode = if is_static {
117            ClassPropertiesSuperConverterMode::StaticPrivateMethod
118        } else {
119            ClassPropertiesSuperConverterMode::PrivateMethod
120        };
121        Self { super_converter: ClassPropertiesSuperConverter::new(mode, class_properties), ctx }
122    }
123}
124
125impl<'a> VisitMut<'a> for PrivateMethodVisitor<'a, '_, '_> {
126    #[inline]
127    fn visit_expression(&mut self, expr: &mut Expression<'a>) {
128        match expr {
129            // `super.prop`
130            Expression::StaticMemberExpression(_) => {
131                self.super_converter.transform_static_member_expression(expr, self.ctx);
132            }
133            // `super[prop]`
134            Expression::ComputedMemberExpression(_) => {
135                self.super_converter.transform_computed_member_expression(expr, self.ctx);
136            }
137            // `super.prop()`
138            Expression::CallExpression(call_expr) => {
139                self.super_converter
140                    .transform_call_expression_for_super_member_expr(call_expr, self.ctx);
141            }
142            // `super.prop = value`, `super.prop += value`, `super.prop ??= value`
143            Expression::AssignmentExpression(_) => {
144                self.super_converter
145                    .transform_assignment_expression_for_super_assignment_target(expr, self.ctx);
146            }
147            // `super.prop++`, `--super.prop`
148            Expression::UpdateExpression(_) => {
149                self.super_converter
150                    .transform_update_expression_for_super_assignment_target(expr, self.ctx);
151            }
152            _ => {}
153        }
154        walk_mut::walk_expression(self, expr);
155    }
156
157    /// Transform reference to class name to temp var
158    fn visit_identifier_reference(&mut self, ident: &mut IdentifierReference<'a>) {
159        self.super_converter.class_properties.replace_class_name_with_temp_var(ident, self.ctx);
160    }
161
162    #[inline]
163    fn visit_class(&mut self, _class: &mut Class<'a>) {
164        // Ignore because we don't need to transform `super` for other classes.
165
166        // TODO: Actually we do need to transform `super` in:
167        // 1. Class decorators
168        // 2. Class `extends` clause
169        // 3. Class property/method/accessor computed keys
170        // 4. Class property/method/accessor decorators
171        //    (or does `super` in a decorator refer to inner class?)
172    }
173}