swc_ecma_compat_es3/
reserved_word.rs

1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
4
5/// babel: `@babel/plugin-transform-reserved-words`
6///
7/// Some words were reserved in ES3 as potential future keywords but were not
8/// reserved in ES5 and later. This plugin, to be used when targeting ES3
9/// environments, renames variables from that set of words.
10///
11/// # Input
12/// ```js
13/// var abstract = 1;
14/// var x = abstract + 1;
15/// ```
16///
17/// # Output
18/// ```js
19/// var _abstract = 1;
20/// var x = _abstract + 1;
21/// ```
22pub fn reserved_words(preserve_import: bool) -> impl Pass {
23    visit_mut_pass(ReservedWord { preserve_import })
24}
25struct ReservedWord {
26    pub preserve_import: bool,
27}
28
29impl VisitMut for ReservedWord {
30    noop_visit_mut_type!(fail);
31
32    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
33        let mut extra_exports = Vec::new();
34
35        n.iter_mut().for_each(|module_item| {
36            match module_item {
37                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
38                    decl: decl @ Decl::Fn(..) | decl @ Decl::Class(..),
39                    ..
40                })) => {
41                    let ident = match decl {
42                        Decl::Class(d) => d.ident.clone(),
43                        Decl::Fn(d) => d.ident.clone(),
44                        _ => {
45                            unreachable!()
46                        }
47                    };
48
49                    if !ident.is_reserved_in_es3() {
50                        return;
51                    }
52
53                    *module_item = decl.take().into();
54
55                    let mut orig = ident.clone();
56                    orig.visit_mut_with(self);
57
58                    extra_exports.push(
59                        ExportNamedSpecifier {
60                            span: DUMMY_SP,
61                            orig: orig.into(),
62                            exported: Some(ident.into()),
63                            is_type_only: false,
64                        }
65                        .into(),
66                    );
67                }
68
69                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
70                    decl: Decl::Var(var),
71                    ..
72                })) => {
73                    if var.decls.iter().all(|var| {
74                        if let Pat::Ident(i) = &var.name {
75                            !i.sym.is_reserved_in_es3()
76                        } else {
77                            true
78                        }
79                    }) {
80                        return;
81                    }
82
83                    for var in &var.decls {
84                        let ident = Ident::from(var.name.clone().expect_ident());
85
86                        if !ident.is_reserved_in_es3() {
87                            return;
88                        }
89
90                        let mut orig = ident.clone();
91                        orig.visit_mut_with(self);
92
93                        extra_exports.push(
94                            ExportNamedSpecifier {
95                                span: DUMMY_SP,
96                                orig: orig.into(),
97                                exported: Some(ident.into()),
98                                is_type_only: false,
99                            }
100                            .into(),
101                        );
102                    }
103
104                    *module_item = var.take().into();
105                }
106
107                _ => {}
108            }
109
110            module_item.visit_mut_with(self);
111        });
112
113        if !extra_exports.is_empty() {
114            let module_item = NamedExport {
115                span: DUMMY_SP,
116                specifiers: extra_exports,
117                src: None,
118                type_only: false,
119                with: None,
120            }
121            .into();
122
123            n.push(module_item);
124        }
125    }
126
127    fn visit_mut_export_named_specifier(&mut self, n: &mut ExportNamedSpecifier) {
128        if matches!(&n.orig, ModuleExportName::Ident(ident) if ident.is_reserved_in_es3()) {
129            n.exported.get_or_insert_with(|| n.orig.clone());
130            n.orig.visit_mut_with(self);
131        }
132    }
133
134    fn visit_mut_named_export(&mut self, n: &mut NamedExport) {
135        if n.src.is_none() {
136            n.visit_mut_children_with(self);
137        }
138    }
139
140    fn visit_mut_ident(&mut self, i: &mut Ident) {
141        if self.preserve_import && i.sym == *"import" {
142            return;
143        }
144
145        if i.is_reserved_in_es3() {
146            i.sym = format!("_{}", i.sym).into()
147        }
148    }
149
150    fn visit_mut_import_named_specifier(&mut self, s: &mut ImportNamedSpecifier) {
151        if s.local.is_reserved_in_es3() {
152            s.imported.get_or_insert_with(|| s.local.clone().into());
153            s.local.visit_mut_with(self);
154        }
155    }
156
157    fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
158        e.obj.visit_mut_with(self);
159
160        if let MemberProp::Computed(c) = &mut e.prop {
161            c.visit_mut_with(self);
162        }
163    }
164
165    fn visit_mut_prop_name(&mut self, _: &mut PropName) {}
166}
167
168#[cfg(test)]
169mod tests {
170    use swc_ecma_transforms_testing::test;
171
172    use super::*;
173
174    macro_rules! identical {
175        ($name:ident, $src:literal) => {
176            test!(
177                ::swc_ecma_parser::Syntax::default(),
178                |_| reserved_words(false),
179                $name,
180                $src
181            );
182        };
183    }
184
185    test!(
186        ::swc_ecma_parser::Syntax::default(),
187        |_| reserved_words(false),
188        babel_issue_6477,
189        r#"
190function utf8CheckByte(byte) {
191  if (byte <= 0x7F) return 0;
192  else if (byte >> 5 === 0x06) return 2;
193  else if (byte >> 4 === 0x0E) return 3;
194  else if (byte >> 3 === 0x1E) return 4;
195  return -1;
196}
197"#
198    );
199
200    identical!(export_as_default, "export { Foo as default }");
201
202    test!(
203        ::swc_ecma_parser::Syntax::default(),
204        |_| reserved_words(false),
205        issue_7164,
206        r#"
207        import { int } from './a.js'
208        console.log(int)
209        export { int };
210        "#
211    );
212
213    test!(
214        Default::default(),
215        |_| reserved_words(false),
216        issue_7237,
217        r#"
218        export function char() {
219            console.log("char====char");
220            return "";
221        }
222        "#
223    );
224}