Skip to main content

next_custom_transforms/transforms/
cjs_finder.rs

1use swc_core::ecma::{
2    ast::*,
3    visit::{Visit, VisitWith},
4};
5
6pub fn contains_cjs(m: &Module) -> bool {
7    let mut v = CjsFinder::default();
8    m.visit_with(&mut v);
9    v.found && !v.is_esm
10}
11
12#[derive(Copy, Clone, Default)]
13struct CjsFinder {
14    found: bool,
15    is_esm: bool,
16    ignore_module: bool,
17    ignore_exports: bool,
18}
19
20impl CjsFinder {
21    /// If the given pattern contains `module` as a parameter, we don't need to
22    /// recurse into it because `module` is shadowed.
23    fn adjust_state<'a, I>(&mut self, iter: I)
24    where
25        I: Iterator<Item = &'a Pat>,
26    {
27        iter.for_each(|p| {
28            if let Pat::Ident(i) = p {
29                if &*i.id.sym == "module" {
30                    self.ignore_module = true;
31                }
32                if &*i.id.sym == "exports" {
33                    self.ignore_exports = true;
34                }
35            }
36        })
37    }
38}
39
40/// This visitor implementation supports typescript, because the api of `swc`
41/// does not support changing configuration based on content of the file.
42impl Visit for CjsFinder {
43    fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
44        let old_ignore_module = self.ignore_module;
45        let old_ignore_exports = self.ignore_exports;
46
47        self.adjust_state(n.params.iter());
48
49        n.visit_children_with(self);
50
51        self.ignore_module = old_ignore_module;
52        self.ignore_exports = old_ignore_exports;
53    }
54
55    // Detect `Object.defineProperty(exports, "__esModule", ...)`
56    // Note that `Object.defineProperty(module.exports, ...)` will be handled by
57    // `visit_member_expr`.
58    fn visit_call_expr(&mut self, e: &CallExpr) {
59        if !self.ignore_exports
60            && let Callee::Expr(expr) = &e.callee
61            && let Expr::Member(member_expr) = &**expr
62            && let (Expr::Ident(obj), MemberProp::Ident(prop)) =
63                (&*member_expr.obj, &member_expr.prop)
64            && &*obj.sym == "Object"
65            && &*prop.sym == "defineProperty"
66            && let Some(ExprOrSpread { expr: expr0, .. }) = e.args.first()
67            && let Expr::Ident(arg0) = &**expr0
68            && &*arg0.sym == "exports"
69            && let Some(ExprOrSpread { expr: expr1, .. }) = e.args.get(1)
70            && let Expr::Lit(Lit::Str(arg1)) = &**expr1
71            && &*arg1.value == "__esModule"
72        {
73            self.found = true;
74            return;
75        }
76
77        e.callee.visit_with(self);
78    }
79
80    fn visit_class_method(&mut self, n: &ClassMethod) {
81        let old_ignore_module = self.ignore_module;
82        let old_ignore_exports = self.ignore_exports;
83
84        self.adjust_state(n.function.params.iter().map(|v| &v.pat));
85
86        n.visit_children_with(self);
87
88        self.ignore_module = old_ignore_module;
89        self.ignore_exports = old_ignore_exports;
90    }
91
92    fn visit_function(&mut self, n: &Function) {
93        let old_ignore_module = self.ignore_module;
94        let old_ignore_exports = self.ignore_exports;
95
96        self.adjust_state(n.params.iter().map(|v| &v.pat));
97
98        n.visit_children_with(self);
99
100        self.ignore_module = old_ignore_module;
101        self.ignore_exports = old_ignore_exports;
102    }
103
104    fn visit_member_expr(&mut self, e: &MemberExpr) {
105        if let Expr::Ident(obj) = &*e.obj
106            && let MemberProp::Ident(prop) = &e.prop
107        {
108            // Detect `module.exports` and `exports.__esModule`
109            if (!self.ignore_module && &*obj.sym == "module" && &*prop.sym == "exports")
110                || (!self.ignore_exports && &*obj.sym == "exports")
111            {
112                self.found = true;
113                return;
114            }
115        }
116
117        e.obj.visit_with(self);
118        e.prop.visit_with(self);
119    }
120
121    fn visit_method_prop(&mut self, n: &MethodProp) {
122        let old_ignore_module = self.ignore_module;
123        let old_ignore_exports = self.ignore_exports;
124
125        self.adjust_state(n.function.params.iter().map(|v| &v.pat));
126
127        n.visit_children_with(self);
128
129        self.ignore_module = old_ignore_module;
130        self.ignore_exports = old_ignore_exports;
131    }
132
133    fn visit_module_decl(&mut self, n: &ModuleDecl) {
134        match n {
135            ModuleDecl::Import(_) => {}
136            _ => {
137                self.is_esm = true;
138            }
139        }
140    }
141}