swc_ecma_compat_es2015/
typeof_symbol.rs

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