swc_ecma_react_compiler/
fast_check.rs

1use swc_ecma_ast::{
2    Callee, ExportDefaultDecl, ExportDefaultExpr, Expr, FnDecl, FnExpr, Pat, Program, Stmt,
3    VarDeclarator,
4};
5use swc_ecma_visit::{Visit, VisitWith};
6pub fn is_required(program: &Program) -> bool {
7    let mut finder = Finder::default();
8    finder.visit_program(program);
9    finder.found
10}
11
12#[derive(Default)]
13struct Finder {
14    found: bool,
15
16    /// We are in a function that starts with a capital letter or it's a
17    /// function that starts with `use`
18    is_interested: bool,
19}
20
21impl Visit for Finder {
22    fn visit_callee(&mut self, node: &Callee) {
23        if self.is_interested {
24            if let Callee::Expr(e) = node {
25                if let Expr::Ident(c) = &**e {
26                    if c.sym.starts_with("use") {
27                        self.found = true;
28                        return;
29                    }
30                }
31            }
32        }
33
34        node.visit_children_with(self);
35    }
36
37    fn visit_export_default_decl(&mut self, node: &ExportDefaultDecl) {
38        let old = self.is_interested;
39
40        self.is_interested = true;
41
42        node.visit_children_with(self);
43
44        self.is_interested = old;
45    }
46
47    fn visit_export_default_expr(&mut self, node: &ExportDefaultExpr) {
48        let old = self.is_interested;
49
50        self.is_interested = true;
51
52        node.visit_children_with(self);
53
54        self.is_interested = old;
55    }
56
57    fn visit_expr(&mut self, node: &Expr) {
58        if self.found {
59            return;
60        }
61        if self.is_interested
62            && matches!(
63                node,
64                Expr::JSXMember(..)
65                    | Expr::JSXNamespacedName(..)
66                    | Expr::JSXEmpty(..)
67                    | Expr::JSXElement(..)
68                    | Expr::JSXFragment(..)
69            )
70        {
71            self.found = true;
72            return;
73        }
74
75        node.visit_children_with(self);
76    }
77
78    fn visit_fn_decl(&mut self, node: &FnDecl) {
79        let old = self.is_interested;
80
81        self.is_interested = node.ident.sym.starts_with("use")
82            || node.ident.sym.starts_with(|c: char| c.is_ascii_uppercase());
83
84        node.visit_children_with(self);
85
86        self.is_interested = old;
87    }
88
89    fn visit_fn_expr(&mut self, node: &FnExpr) {
90        let old = self.is_interested;
91
92        self.is_interested |= node.ident.as_ref().is_some_and(|ident| {
93            ident.sym.starts_with("use") || ident.sym.starts_with(|c: char| c.is_ascii_uppercase())
94        });
95
96        node.visit_children_with(self);
97
98        self.is_interested = old;
99    }
100
101    fn visit_stmt(&mut self, node: &Stmt) {
102        if self.found {
103            return;
104        }
105        node.visit_children_with(self);
106    }
107
108    fn visit_var_declarator(&mut self, node: &VarDeclarator) {
109        let old = self.is_interested;
110
111        if matches!(node.init.as_deref(), Some(Expr::Fn(..) | Expr::Arrow(..))) {
112            if let Pat::Ident(ident) = &node.name {
113                self.is_interested = ident.sym.starts_with("use")
114                    || ident.sym.starts_with(|c: char| c.is_ascii_uppercase());
115            } else {
116                self.is_interested = false;
117            }
118        }
119
120        node.visit_children_with(self);
121
122        self.is_interested = old;
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use swc_common::FileName;
129    use swc_ecma_parser::{parse_file_as_program, EsSyntax, Syntax};
130    use testing::run_test2;
131
132    use super::*;
133
134    fn assert_required(code: &str, required: bool) {
135        run_test2(false, |cm, _| {
136            let fm =
137                cm.new_source_file(FileName::Custom("test.tsx".into()).into(), code.to_string());
138
139            let program = parse_file_as_program(
140                &fm,
141                Syntax::Es(EsSyntax {
142                    jsx: true,
143                    ..Default::default()
144                }),
145                Default::default(),
146                Default::default(),
147                &mut vec![],
148            )
149            .unwrap();
150
151            assert_eq!(is_required(&program), required);
152
153            Ok(())
154        })
155        .unwrap();
156    }
157
158    #[test]
159    fn lazy_return() {
160        assert_required(
161            "
162            function Foo() {
163                const a = <div>Hello</div>;
164
165                return a
166            }
167            ",
168            true,
169        );
170
171        assert_required(
172            "
173            function Foo() {
174            ",
175            false,
176        );
177    }
178
179    #[test]
180    fn return_jsx() {
181        assert_required(
182            "
183            function Foo() {
184                return <div>Hello</div>;
185            }
186            ",
187            true,
188        );
189    }
190
191    #[test]
192    fn use_hooks() {
193        assert_required(
194            "
195            function Foo(props) {
196                const [a, b] = useState(0);
197
198                return props.children;
199            }
200            ",
201            true,
202        );
203    }
204
205    #[test]
206    fn arrow_function() {
207        assert_required(
208            "
209            const Foo = () => <div>Hello</div>;
210            ",
211            true,
212        );
213
214        assert_required(
215            "
216            const Foo = () => {
217                return <div>Hello</div>;
218            };
219            ",
220            true,
221        );
222    }
223
224    #[test]
225    fn export_const_arrow_function() {
226        assert_required(
227            "
228            export const Foo = () => <div>Hello</div>;
229            ",
230            true,
231        );
232
233        assert_required(
234            "
235            export const Foo = () => {
236                return <div>Hello</div>;
237            };
238            ",
239            true,
240        );
241    }
242
243    #[test]
244    fn normal_arrow_function() {
245        assert_required(
246            "
247            const Foo = () => {
248                const a = 1;
249                console.log(a);
250            };
251            ",
252            false,
253        );
254    }
255
256    #[test]
257    fn export_default_arrow_function() {
258        assert_required(
259            "
260            export default () => <div>Hello</div>;
261            ",
262            true,
263        );
264    }
265
266    #[test]
267    fn not_required_arrow_function() {
268        assert_required(
269            "
270            export default () => {
271                const a = 1;
272                console.log(a);
273            };
274            ",
275            false,
276        );
277    }
278}