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                    match &*s.value {
141                        "@swc/helpers - typeof" | "@babel/helpers - typeof" => return,
142                        _ => {}
143                    }
144                }
145            }
146        }
147
148        f.visit_mut_children_with(self);
149    }
150}
151
152#[tracing::instrument(level = "debug", skip_all)]
153fn is_non_symbol_literal(e: &Expr) -> bool {
154    match e {
155        Expr::Lit(Lit::Str(Str { value, .. })) => matches!(
156            &**value,
157            "undefined" | "boolean" | "number" | "string" | "function"
158        ),
159        _ => false,
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use swc_ecma_parser::Syntax;
166    use swc_ecma_transforms_testing::test;
167
168    use super::*;
169
170    test!(
171        Syntax::default(),
172        |_| typeof_symbol(Config::default()),
173        dont_touch_non_symbol_comparison,
174        "typeof window !== 'undefined'"
175    );
176
177    test!(
178        Syntax::default(),
179        |_| typeof_symbol(Config::default()),
180        dont_touch_non_symbol_comparison_02,
181        "'undefined' !== typeof window"
182    );
183
184    test!(
185        Syntax::default(),
186        |_| typeof_symbol(Config::default()),
187        issue_1843_1,
188        "
189        function isUndef(type) {
190            return type === 'undefined';
191        }
192
193        var isWeb = !isUndef(typeof window) && 'onload' in window;
194        exports.isWeb = isWeb;
195        var isNode = !isUndef(typeof process) && !!(process.versions && process.versions.node);
196        exports.isNode = isNode;
197        var isWeex = !isUndef(typeof WXEnvironment) && WXEnvironment.platform !== 'Web';
198        exports.isWeex = isWeex;
199        "
200    );
201
202    test!(
203        Syntax::default(),
204        |_| typeof_symbol(Config { loose: true }),
205        dont_touch_in_loose_mode,
206        "typeof sym;"
207    );
208}