swc_ecma_compat_es2015/
typeof_symbol.rs

1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_transforms_base::{helper, perf::Parallel};
4use swc_ecma_utils::{quote_str, ExprFactory};
5use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
6use swc_trace_macro::swc_trace;
7
8pub fn typeof_symbol() -> impl Pass {
9    visit_mut_pass(TypeOfSymbol)
10}
11
12#[derive(Clone, Copy)]
13struct TypeOfSymbol;
14
15#[swc_trace]
16impl Parallel for TypeOfSymbol {
17    fn merge(&mut self, _: Self) {}
18
19    fn create(&self) -> Self {
20        TypeOfSymbol
21    }
22}
23
24#[swc_trace]
25impl VisitMut for TypeOfSymbol {
26    noop_visit_mut_type!(fail);
27
28    fn visit_mut_bin_expr(&mut self, expr: &mut BinExpr) {
29        match expr.op {
30            op!("==") | op!("!=") | op!("===") | op!("!==") => {}
31            _ => {
32                expr.visit_mut_children_with(self);
33                return;
34            }
35        }
36
37        if let Expr::Unary(UnaryExpr {
38            op: op!("typeof"), ..
39        }) = *expr.left
40        {
41            if is_non_symbol_literal(&expr.right) {
42                return;
43            }
44        }
45        if let Expr::Unary(UnaryExpr {
46            op: op!("typeof"), ..
47        }) = *expr.right
48        {
49            if is_non_symbol_literal(&expr.left) {
50                return;
51            }
52        }
53
54        expr.visit_mut_children_with(self)
55    }
56
57    fn visit_mut_expr(&mut self, expr: &mut Expr) {
58        expr.visit_mut_children_with(self);
59
60        if let Expr::Unary(UnaryExpr {
61            span,
62            op: op!("typeof"),
63            arg,
64        }) = expr
65        {
66            match &**arg {
67                Expr::Ident(..) => {
68                    let undefined_str: Box<Expr> = quote_str!("undefined").into();
69
70                    let test = BinExpr {
71                        span: DUMMY_SP,
72                        op: op!("==="),
73                        left: Box::new(
74                            UnaryExpr {
75                                span: DUMMY_SP,
76                                op: op!("typeof"),
77                                arg: arg.clone(),
78                            }
79                            .into(),
80                        ),
81                        right: undefined_str.clone(),
82                    }
83                    .into();
84
85                    let call = CallExpr {
86                        span: *span,
87                        callee: helper!(*span, type_of),
88                        args: vec![arg.take().as_arg()],
89                        ..Default::default()
90                    }
91                    .into();
92
93                    *expr = CondExpr {
94                        span: *span,
95                        test,
96                        cons: undefined_str,
97                        alt: Box::new(call),
98                    }
99                    .into();
100                }
101                _ => {
102                    let call = CallExpr {
103                        span: *span,
104                        callee: helper!(*span, type_of),
105                        args: vec![arg.take().as_arg()],
106
107                        ..Default::default()
108                    }
109                    .into();
110
111                    *expr = call;
112                }
113            }
114        }
115    }
116
117    fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
118        if &f.ident.sym == "_type_of" {
119            return;
120        }
121
122        f.visit_mut_children_with(self);
123    }
124
125    fn visit_mut_function(&mut self, f: &mut Function) {
126        if let Some(body) = &f.body {
127            if let Some(Stmt::Expr(first)) = body.stmts.first() {
128                if let Expr::Lit(Lit::Str(s)) = &*first.expr {
129                    match &*s.value {
130                        "@swc/helpers - typeof" | "@babel/helpers - typeof" => return,
131                        _ => {}
132                    }
133                }
134            }
135        }
136
137        f.visit_mut_children_with(self);
138    }
139}
140
141#[tracing::instrument(level = "debug", skip_all)]
142fn is_non_symbol_literal(e: &Expr) -> bool {
143    match e {
144        Expr::Lit(Lit::Str(Str { value, .. })) => matches!(
145            &**value,
146            "undefined" | "boolean" | "number" | "string" | "function"
147        ),
148        _ => false,
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use swc_ecma_parser::Syntax;
155    use swc_ecma_transforms_testing::test;
156
157    use super::*;
158
159    test!(
160        Syntax::default(),
161        |_| typeof_symbol(),
162        dont_touch_non_symbol_comparison,
163        "typeof window !== 'undefined'"
164    );
165
166    test!(
167        Syntax::default(),
168        |_| typeof_symbol(),
169        dont_touch_non_symbol_comparison_02,
170        "'undefined' !== typeof window"
171    );
172
173    test!(
174        Syntax::default(),
175        |_| typeof_symbol(),
176        issue_1843_1,
177        "
178        function isUndef(type) {
179            return type === 'undefined';
180        }
181
182        var isWeb = !isUndef(typeof window) && 'onload' in window;
183        exports.isWeb = isWeb;
184        var isNode = !isUndef(typeof process) && !!(process.versions && process.versions.node);
185        exports.isNode = isNode;
186        var isWeex = !isUndef(typeof WXEnvironment) && WXEnvironment.platform !== 'Web';
187        exports.isWeex = isWeex;
188        "
189    );
190}