swc_ecma_compat_es2016/
exponentiation.rs

1use swc_common::{util::take::Take, Span, Spanned, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_transforms_base::perf::{ParExplode, Parallel};
4use swc_ecma_transforms_macros::parallel;
5use swc_ecma_utils::{member_expr, private_ident, ExprFactory};
6use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
7use swc_trace_macro::swc_trace;
8
9/// `@babel/plugin-transform-exponentiation-operator`
10///
11/// # Example
12///
13/// ## In
14///
15/// ```js
16/// let x = 10 ** 2;
17///
18/// x **= 3;
19/// ```
20///
21/// ## Out
22///
23/// ```js
24/// let x = Math.pow(10, 2);
25///
26/// x = Math.pow(x, 3);
27/// ```
28pub fn exponentiation() -> impl Pass {
29    visit_mut_pass(Exponentiation::default())
30}
31
32#[derive(Default)]
33struct Exponentiation {
34    vars: Vec<VarDeclarator>,
35}
36
37impl Parallel for Exponentiation {
38    fn create(&self) -> Self {
39        Self::default()
40    }
41
42    fn merge(&mut self, other: Self) {
43        self.vars.extend(other.vars);
44    }
45}
46
47#[swc_trace]
48impl ParExplode for Exponentiation {
49    fn after_one_stmt(&mut self, stmts: &mut Vec<Stmt>) {
50        if !self.vars.is_empty() {
51            stmts.push(
52                VarDecl {
53                    span: DUMMY_SP,
54                    kind: VarDeclKind::Var,
55                    decls: self.vars.take(),
56                    declare: false,
57                    ..Default::default()
58                }
59                .into(),
60            );
61        }
62    }
63
64    fn after_one_module_item(&mut self, stmts: &mut Vec<ModuleItem>) {
65        if !self.vars.is_empty() {
66            stmts.push(
67                VarDecl {
68                    span: DUMMY_SP,
69                    kind: VarDeclKind::Var,
70                    decls: self.vars.take(),
71                    declare: false,
72                    ..Default::default()
73                }
74                .into(),
75            );
76        }
77    }
78}
79
80#[swc_trace]
81#[parallel(explode)]
82impl VisitMut for Exponentiation {
83    noop_visit_mut_type!(fail);
84
85    fn visit_mut_expr(&mut self, e: &mut Expr) {
86        e.visit_mut_children_with(self);
87
88        match e {
89            Expr::Assign(AssignExpr {
90                span,
91                left,
92                op: op @ op!("**="),
93                right,
94            }) => {
95                let lhs: Ident = match left {
96                    _ if left.as_ident().is_some() => left.as_ident().unwrap().clone().into(),
97
98                    // unimplemented
99                    AssignTarget::Simple(ref e) => {
100                        let ref_ident = private_ident!(e.span(), "ref");
101
102                        self.vars.push(VarDeclarator {
103                            span: DUMMY_SP,
104                            name: ref_ident.clone().into(),
105                            init: Some(e.clone().into()),
106                            definite: false,
107                        });
108                        ref_ident
109                    }
110
111                    left => {
112                        *e = AssignExpr {
113                            span: *span,
114                            left: left.take(),
115                            op: op!("="),
116                            right: right.take(),
117                        }
118                        .into();
119                        return;
120                    }
121                };
122
123                *op = op!("=");
124                *right = Box::new(mk_call(*span, Box::new(lhs.into()), right.take()));
125            }
126            Expr::Bin(BinExpr {
127                span,
128                left,
129                op: op!("**"),
130                right,
131            }) => {
132                *e = mk_call(*span, left.take(), right.take());
133            }
134            _ => {}
135        }
136    }
137}
138
139#[tracing::instrument(level = "debug", skip_all)]
140fn mk_call(span: Span, left: Box<Expr>, right: Box<Expr>) -> Expr {
141    // Math.pow()
142    CallExpr {
143        span,
144        callee: member_expr!(Default::default(), span, Math.pow).as_callee(),
145
146        args: vec![left.as_arg(), right.as_arg()],
147        ..Default::default()
148    }
149    .into()
150}
151
152#[cfg(test)]
153mod tests {
154    use swc_ecma_transforms_testing::{test, test_exec};
155
156    use super::*;
157
158    test!(
159        ::swc_ecma_parser::Syntax::default(),
160        |_| exponentiation(),
161        babel_binary,
162        "2 ** 2"
163    );
164
165    test_exec!(
166        ignore,
167        ::swc_ecma_parser::Syntax::default(),
168        |_| exponentiation(),
169        babel_comprehensive,
170        r#"expect(2 ** 3).toBe(8);
171expect(3 * 2 ** 3).toBe(24);
172var x = 2;
173expect(2 ** ++x).toBe(8);
174expect(2 ** -1 * 2).toBe(1);
175
176var calls = 0;
177var q = {q: 3};
178var o = {
179  get p() {
180    calls++;
181    return q;
182  }
183};
184
185o.p.q **= 2;
186expect(calls).toBe(1);
187expect(o.p.q).toBe(9);
188
189expect(2 ** (3 ** 2)).toBe(512);
190expect(2 ** 3 ** 2).toBe(512);"#
191    );
192
193    test_exec!(
194        // FIXME
195        ignore,
196        ::swc_ecma_parser::Syntax::default(),
197        |_| exponentiation(),
198        babel_memoize_object,
199        r#"var counters = 0;
200Object.defineProperty(global, "reader", {
201  get: function () {
202    counters += 1;
203    return { x: 2 };
204  },
205  configurable: true
206});
207reader.x **= 2;
208expect(counters).toBe(1);"#
209    );
210
211    test!(
212        ::swc_ecma_parser::Syntax::default(),
213        |_| exponentiation(),
214        assign,
215        r#"x **= 3"#,
216        ok_if_code_eq
217    );
218
219    //     test!(::swc_ecma_parser::Syntax::default(),
220    //         |_| exponentiation(),
221    //         babel_4403,
222    //         "var a, b;
223    // a[`${b++}`] **= 1;",
224    //         "var _ref;
225
226    // var a, b;
227    // _ref = `${b++}`, a[_ref] = Math.pow(a[_ref], 1);"
228    //     );
229
230    test!(
231        ::swc_ecma_parser::Syntax::default(),
232        |_| exponentiation(),
233        issue_740,
234        "self.a = 10 ** 2",
235        ok_if_code_eq
236    );
237
238    // https://github.com/swc-project/swc/pull/741/files
239    // bu JakeChampion
240    test!(
241        ::swc_ecma_parser::Syntax::default(),
242        |_| exponentiation(),
243        babel_binary_member_assignment_expression,
244        "var x = {}; x.a = 2 ** 2"
245    );
246
247    test!(
248        ::swc_ecma_parser::Syntax::default(),
249        |_| exponentiation(),
250        assign_to_object_property,
251        r#"var self = {}; self.x **= 3"#,
252        ok_if_code_eq
253    );
254}