swc_ecma_compat_es2015/
typeof_symbol.rs1use 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}