swc_ecma_react_compiler/
fast_check.rs1use 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 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}