@samitouri / QOS-React-2 / commits / d3da200820

[compiler] Remove OXC and SWC plugins in favor of them being handled by those projects (#36743)

I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate --------- Co-authored-by: mvitousek <mvitousek@devvm12588.pnb0.facebook.com>

Michael Vitousek committed Jun 11, 2026 at 15:41 UTC d3da2008202390d8bf532cb55d47e184b38c89a9
105 files changed +2724 -19722
compiler/Cargo.lock
+103 -1711
@@ -2,18 +2,6 @@
2 # It is not intended for manual editing.
3 version = 4
4
5 -[[package]]
6 -name = "ahash"
7 -version = "0.8.12"
8 -source = "registry+https://github.com/rust-lang/crates.io-index"
9 -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
10 -dependencies = [
11 - "cfg-if",
12 - "once_cell",
13 - "version_check",
14 - "zerocopy",
15 -]
16 -
5 [[package]]
6 name = "aho-corasick"
7 version = "1.1.4"
@@ -23,119 +11,6 @@ dependencies = [
11 "memchr",
12 ]
13
26 -[[package]]
27 -name = "allocator-api2"
28 -version = "0.2.21"
29 -source = "registry+https://github.com/rust-lang/crates.io-index"
30 -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
31 -
32 -[[package]]
33 -name = "anstream"
34 -version = "1.0.0"
35 -source = "registry+https://github.com/rust-lang/crates.io-index"
36 -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
37 -dependencies = [
38 - "anstyle",
39 - "anstyle-parse",
40 - "anstyle-query",
41 - "anstyle-wincon",
42 - "colorchoice",
43 - "is_terminal_polyfill",
44 - "utf8parse",
45 -]
46 -
47 -[[package]]
48 -name = "anstyle"
49 -version = "1.0.14"
50 -source = "registry+https://github.com/rust-lang/crates.io-index"
51 -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
52 -
53 -[[package]]
54 -name = "anstyle-parse"
55 -version = "1.0.0"
56 -source = "registry+https://github.com/rust-lang/crates.io-index"
57 -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
58 -dependencies = [
59 - "utf8parse",
60 -]
61 -
62 -[[package]]
63 -name = "anstyle-query"
64 -version = "1.1.5"
65 -source = "registry+https://github.com/rust-lang/crates.io-index"
66 -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
67 -dependencies = [
68 - "windows-sys 0.61.2",
69 -]
70 -
71 -[[package]]
72 -name = "anstyle-wincon"
73 -version = "3.0.11"
74 -source = "registry+https://github.com/rust-lang/crates.io-index"
75 -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
76 -dependencies = [
77 - "anstyle",
78 - "once_cell_polyfill",
79 - "windows-sys 0.61.2",
80 -]
81 -
82 -[[package]]
83 -name = "anyhow"
84 -version = "1.0.102"
85 -source = "registry+https://github.com/rust-lang/crates.io-index"
86 -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
87 -
88 -[[package]]
89 -name = "ar_archive_writer"
90 -version = "0.5.1"
91 -source = "registry+https://github.com/rust-lang/crates.io-index"
92 -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
93 -dependencies = [
94 - "object",
95 -]
96 -
97 -[[package]]
98 -name = "ascii"
99 -version = "1.1.0"
100 -source = "registry+https://github.com/rust-lang/crates.io-index"
101 -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
102 -
103 -[[package]]
104 -name = "ast_node"
105 -version = "5.0.0"
106 -source = "registry+https://github.com/rust-lang/crates.io-index"
107 -checksum = "2eb025ef00a6da925cf40870b9c8d008526b6004ece399cb0974209720f0b194"
108 -dependencies = [
109 - "quote",
110 - "swc_macros_common",
111 - "syn",
112 -]
113 -
114 -[[package]]
115 -name = "autocfg"
116 -version = "1.5.0"
117 -source = "registry+https://github.com/rust-lang/crates.io-index"
118 -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
119 -
120 -[[package]]
121 -name = "base64-simd"
122 -version = "0.8.0"
123 -source = "registry+https://github.com/rust-lang/crates.io-index"
124 -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195"
125 -dependencies = [
126 - "outref",
127 - "vsimd",
128 -]
129 -
130 -[[package]]
131 -name = "better_scoped_tls"
132 -version = "1.0.1"
133 -source = "registry+https://github.com/rust-lang/crates.io-index"
134 -checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609"
135 -dependencies = [
136 - "scoped-tls",
137 -]
138 -
14 [[package]]
15 name = "bitflags"
16 version = "2.11.0"
@@ -151,129 +26,12 @@ dependencies = [
26 "generic-array",
27 ]
28
154 -[[package]]
155 -name = "bumpalo"
156 -version = "3.20.2"
157 -source = "registry+https://github.com/rust-lang/crates.io-index"
158 -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
159 -dependencies = [
160 - "allocator-api2",
161 -]
162 -
163 -[[package]]
164 -name = "bytes"
165 -version = "1.11.1"
166 -source = "registry+https://github.com/rust-lang/crates.io-index"
167 -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
168 -
169 -[[package]]
170 -name = "bytes-str"
171 -version = "0.2.7"
172 -source = "registry+https://github.com/rust-lang/crates.io-index"
173 -checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3"
174 -dependencies = [
175 - "bytes",
176 - "serde",
177 -]
178 -
179 -[[package]]
180 -name = "castaway"
181 -version = "0.2.4"
182 -source = "registry+https://github.com/rust-lang/crates.io-index"
183 -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
184 -dependencies = [
185 - "rustversion",
186 -]
187 -
188 -[[package]]
189 -name = "cc"
190 -version = "1.2.57"
191 -source = "registry+https://github.com/rust-lang/crates.io-index"
192 -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
193 -dependencies = [
194 - "find-msvc-tools",
195 - "shlex",
196 -]
197 -
29 [[package]]
30 name = "cfg-if"
31 version = "1.0.4"
32 source = "registry+https://github.com/rust-lang/crates.io-index"
33 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
34
204 -[[package]]
205 -name = "clap"
206 -version = "4.6.0"
207 -source = "registry+https://github.com/rust-lang/crates.io-index"
208 -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
209 -dependencies = [
210 - "clap_builder",
211 - "clap_derive",
212 -]
213 -
214 -[[package]]
215 -name = "clap_builder"
216 -version = "4.6.0"
217 -source = "registry+https://github.com/rust-lang/crates.io-index"
218 -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
219 -dependencies = [
220 - "anstream",
221 - "anstyle",
222 - "clap_lex",
223 - "strsim",
224 -]
225 -
226 -[[package]]
227 -name = "clap_derive"
228 -version = "4.6.0"
229 -source = "registry+https://github.com/rust-lang/crates.io-index"
230 -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
231 -dependencies = [
232 - "heck",
233 - "proc-macro2",
234 - "quote",
235 - "syn",
236 -]
237 -
238 -[[package]]
239 -name = "clap_lex"
240 -version = "1.1.0"
241 -source = "registry+https://github.com/rust-lang/crates.io-index"
242 -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
243 -
244 -[[package]]
245 -name = "colorchoice"
246 -version = "1.0.5"
247 -source = "registry+https://github.com/rust-lang/crates.io-index"
248 -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
249 -
250 -[[package]]
251 -name = "compact_str"
252 -version = "0.7.1"
253 -source = "registry+https://github.com/rust-lang/crates.io-index"
254 -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
255 -dependencies = [
256 - "castaway",
257 - "cfg-if",
258 - "itoa",
259 - "ryu",
260 - "static_assertions",
261 -]
262 -
263 -[[package]]
264 -name = "compact_str"
265 -version = "0.9.0"
266 -source = "registry+https://github.com/rust-lang/crates.io-index"
267 -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
268 -dependencies = [
269 - "castaway",
270 - "cfg-if",
271 - "itoa",
272 - "rustversion",
273 - "ryu",
274 - "static_assertions",
275 -]
276 -
35 [[package]]
36 name = "convert_case"
37 version = "0.6.0"
@@ -283,12 +41,6 @@ dependencies = [
41 "unicode-segmentation",
42 ]
43
286 -[[package]]
287 -name = "cow-utils"
288 -version = "0.1.3"
289 -source = "registry+https://github.com/rust-lang/crates.io-index"
290 -checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79"
291 -
44 [[package]]
45 name = "cpufeatures"
46 version = "0.2.17"
@@ -329,66 +81,12 @@ dependencies = [
81 "subtle",
82 ]
83
332 -[[package]]
333 -name = "displaydoc"
334 -version = "0.2.5"
335 -source = "registry+https://github.com/rust-lang/crates.io-index"
336 -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
337 -dependencies = [
338 - "proc-macro2",
339 - "quote",
340 - "syn",
341 -]
342 -
343 -[[package]]
344 -name = "dragonbox_ecma"
345 -version = "0.1.12"
346 -source = "registry+https://github.com/rust-lang/crates.io-index"
347 -checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf"
348 -
349 -[[package]]
350 -name = "either"
351 -version = "1.15.0"
352 -source = "registry+https://github.com/rust-lang/crates.io-index"
353 -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
354 -
84 [[package]]
85 name = "equivalent"
86 version = "1.0.2"
87 source = "registry+https://github.com/rust-lang/crates.io-index"
88 checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
89
361 -[[package]]
362 -name = "fastrand"
363 -version = "2.3.0"
364 -source = "registry+https://github.com/rust-lang/crates.io-index"
365 -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
366 -
367 -[[package]]
368 -name = "find-msvc-tools"
369 -version = "0.1.9"
370 -source = "registry+https://github.com/rust-lang/crates.io-index"
371 -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
372 -
373 -[[package]]
374 -name = "form_urlencoded"
375 -version = "1.2.2"
376 -source = "registry+https://github.com/rust-lang/crates.io-index"
377 -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
378 -dependencies = [
379 - "percent-encoding",
380 -]
381 -
382 -[[package]]
383 -name = "from_variant"
384 -version = "3.0.0"
385 -source = "registry+https://github.com/rust-lang/crates.io-index"
386 -checksum = "e5ff35a391aef949120a0340d690269b3d9f63460a6106e99bd07b961f345ea9"
387 -dependencies = [
388 - "swc_macros_common",
389 - "syn",
390 -]
391 -
90 [[package]]
91 name = "generic-array"
92 version = "0.14.7"
@@ -399,30 +97,11 @@ dependencies = [
97 "version_check",
98 ]
99
402 -[[package]]
403 -name = "hashbrown"
404 -version = "0.14.5"
405 -source = "registry+https://github.com/rust-lang/crates.io-index"
406 -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
407 -dependencies = [
408 - "ahash",
409 - "allocator-api2",
410 -]
411 -
100 [[package]]
101 name = "hashbrown"
102 version = "0.16.1"
103 source = "registry+https://github.com/rust-lang/crates.io-index"
104 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
417 -dependencies = [
418 - "allocator-api2",
419 -]
420 -
421 -[[package]]
422 -name = "heck"
423 -version = "0.5.0"
424 -source = "registry+https://github.com/rust-lang/crates.io-index"
425 -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
105
106 [[package]]
107 name = "hmac"
@@ -433,122 +112,6 @@ dependencies = [
112 "digest",
113 ]
114
436 -[[package]]
437 -name = "hstr"
438 -version = "3.0.4"
439 -source = "registry+https://github.com/rust-lang/crates.io-index"
440 -checksum = "faa57007c3c9dab34df2fa4c1fb52fe9c34ec5a27ed9d8edea53254b50cd7887"
441 -dependencies = [
442 - "hashbrown 0.14.5",
443 - "new_debug_unreachable",
444 - "once_cell",
445 - "rustc-hash",
446 - "serde",
447 - "triomphe",
448 -]
449 -
450 -[[package]]
451 -name = "icu_collections"
452 -version = "2.1.1"
453 -source = "registry+https://github.com/rust-lang/crates.io-index"
454 -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
455 -dependencies = [
456 - "displaydoc",
457 - "potential_utf",
458 - "yoke",
459 - "zerofrom",
460 - "zerovec",
461 -]
462 -
463 -[[package]]
464 -name = "icu_locale_core"
465 -version = "2.1.1"
466 -source = "registry+https://github.com/rust-lang/crates.io-index"
467 -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
468 -dependencies = [
469 - "displaydoc",
470 - "litemap",
471 - "tinystr",
472 - "writeable",
473 - "zerovec",
474 -]
475 -
476 -[[package]]
477 -name = "icu_normalizer"
478 -version = "2.1.1"
479 -source = "registry+https://github.com/rust-lang/crates.io-index"
480 -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
481 -dependencies = [
482 - "icu_collections",
483 - "icu_normalizer_data",
484 - "icu_properties",
485 - "icu_provider",
486 - "smallvec",
487 - "zerovec",
488 -]
489 -
490 -[[package]]
491 -name = "icu_normalizer_data"
492 -version = "2.1.1"
493 -source = "registry+https://github.com/rust-lang/crates.io-index"
494 -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
495 -
496 -[[package]]
497 -name = "icu_properties"
498 -version = "2.1.2"
499 -source = "registry+https://github.com/rust-lang/crates.io-index"
500 -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
501 -dependencies = [
502 - "icu_collections",
503 - "icu_locale_core",
504 - "icu_properties_data",
505 - "icu_provider",
506 - "zerotrie",
507 - "zerovec",
508 -]
509 -
510 -[[package]]
511 -name = "icu_properties_data"
512 -version = "2.1.2"
513 -source = "registry+https://github.com/rust-lang/crates.io-index"
514 -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
515 -
516 -[[package]]
517 -name = "icu_provider"
518 -version = "2.1.1"
519 -source = "registry+https://github.com/rust-lang/crates.io-index"
520 -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
521 -dependencies = [
522 - "displaydoc",
523 - "icu_locale_core",
524 - "writeable",
525 - "yoke",
526 - "zerofrom",
527 - "zerotrie",
528 - "zerovec",
529 -]
530 -
531 -[[package]]
532 -name = "idna"
533 -version = "1.1.0"
534 -source = "registry+https://github.com/rust-lang/crates.io-index"
535 -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
536 -dependencies = [
537 - "idna_adapter",
538 - "smallvec",
539 - "utf8_iter",
540 -]
541 -
542 -[[package]]
543 -name = "idna_adapter"
544 -version = "1.2.1"
545 -source = "registry+https://github.com/rust-lang/crates.io-index"
546 -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
547 -dependencies = [
548 - "icu_normalizer",
549 - "icu_properties",
550 -]
551 -
115 [[package]]
116 name = "indexmap"
117 version = "2.13.0"
@@ -556,50 +119,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
119 checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
120 dependencies = [
121 "equivalent",
559 - "hashbrown 0.16.1",
122 + "hashbrown",
123 "serde",
124 "serde_core",
125 ]
126
564 -[[package]]
565 -name = "is-macro"
566 -version = "0.3.7"
567 -source = "registry+https://github.com/rust-lang/crates.io-index"
568 -checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"
569 -dependencies = [
570 - "heck",
571 - "proc-macro2",
572 - "quote",
573 - "syn",
574 -]
575 -
576 -[[package]]
577 -name = "is_terminal_polyfill"
578 -version = "1.70.2"
579 -source = "registry+https://github.com/rust-lang/crates.io-index"
580 -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
581 -
582 -[[package]]
583 -name = "itertools"
584 -version = "0.14.0"
585 -source = "registry+https://github.com/rust-lang/crates.io-index"
586 -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
587 -dependencies = [
588 - "either",
589 -]
590 -
127 [[package]]
128 name = "itoa"
129 version = "1.0.17"
130 source = "registry+https://github.com/rust-lang/crates.io-index"
131 checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
132
597 -[[package]]
598 -name = "json-escape-simd"
599 -version = "3.0.1"
600 -source = "registry+https://github.com/rust-lang/crates.io-index"
601 -checksum = "a3c2a6c0b4b5637c41719973ef40c6a1cf564f9db6958350de6193fbee9c23f5"
602 -
133 [[package]]
134 name = "libc"
135 version = "0.2.183"
@@ -616,12 +146,6 @@ dependencies = [
146 "windows-link",
147 ]
148
619 -[[package]]
620 -name = "litemap"
621 -version = "0.8.1"
622 -source = "registry+https://github.com/rust-lang/crates.io-index"
623 -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
624 -
149 [[package]]
150 name = "memchr"
151 version = "2.8.0"
@@ -686,494 +210,29 @@ dependencies = [
210 ]
211
212 [[package]]
689 -name = "new_debug_unreachable"
690 -version = "1.0.6"
691 -source = "registry+https://github.com/rust-lang/crates.io-index"
692 -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
693 -
694 -[[package]]
695 -name = "nonmax"
696 -version = "0.5.5"
213 +name = "once_cell"
214 +version = "1.21.4"
215 source = "registry+https://github.com/rust-lang/crates.io-index"
698 -checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
216 +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
217
218 [[package]]
701 -name = "num-bigint"
702 -version = "0.4.6"
219 +name = "proc-macro2"
220 +version = "1.0.106"
221 source = "registry+https://github.com/rust-lang/crates.io-index"
704 -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
222 +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
223 dependencies = [
706 - "num-integer",
707 - "num-traits",
708 - "serde",
224 + "unicode-ident",
225 ]
226
227 [[package]]
712 -name = "num-integer"
713 -version = "0.1.46"
714 -source = "registry+https://github.com/rust-lang/crates.io-index"
715 -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
716 -dependencies = [
717 - "num-traits",
718 -]
719 -
720 -[[package]]
721 -name = "num-traits"
722 -version = "0.2.19"
723 -source = "registry+https://github.com/rust-lang/crates.io-index"
724 -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
725 -dependencies = [
726 - "autocfg",
727 -]
728 -
729 -[[package]]
730 -name = "object"
731 -version = "0.37.3"
732 -source = "registry+https://github.com/rust-lang/crates.io-index"
733 -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
734 -dependencies = [
735 - "memchr",
736 -]
737 -
738 -[[package]]
739 -name = "once_cell"
740 -version = "1.21.4"
741 -source = "registry+https://github.com/rust-lang/crates.io-index"
742 -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
743 -
744 -[[package]]
745 -name = "once_cell_polyfill"
746 -version = "1.70.2"
747 -source = "registry+https://github.com/rust-lang/crates.io-index"
748 -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
749 -
750 -[[package]]
751 -name = "outref"
752 -version = "0.5.2"
753 -source = "registry+https://github.com/rust-lang/crates.io-index"
754 -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
755 -
756 -[[package]]
757 -name = "owo-colors"
758 -version = "4.3.0"
759 -source = "registry+https://github.com/rust-lang/crates.io-index"
760 -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
761 -
762 -[[package]]
763 -name = "oxc-miette"
764 -version = "2.7.0"
765 -source = "registry+https://github.com/rust-lang/crates.io-index"
766 -checksum = "60a7ba54c704edefead1f44e9ef09c43e5cfae666bdc33516b066011f0e6ebf7"
767 -dependencies = [
768 - "cfg-if",
769 - "owo-colors",
770 - "oxc-miette-derive",
771 - "textwrap",
772 - "thiserror",
773 - "unicode-segmentation",
774 - "unicode-width",
775 -]
776 -
777 -[[package]]
778 -name = "oxc-miette-derive"
779 -version = "2.7.0"
780 -source = "registry+https://github.com/rust-lang/crates.io-index"
781 -checksum = "d4faecb54d0971f948fbc1918df69b26007e6f279a204793669542e1e8b75eb3"
782 -dependencies = [
783 - "proc-macro2",
784 - "quote",
785 - "syn",
786 -]
787 -
788 -[[package]]
789 -name = "oxc_allocator"
790 -version = "0.121.0"
791 -source = "registry+https://github.com/rust-lang/crates.io-index"
792 -checksum = "c17ece0d1edc5e92822be95428460bc6b12f0dce8f95a9efabf751189a75f9f2"
793 -dependencies = [
794 - "allocator-api2",
795 - "hashbrown 0.16.1",
796 - "oxc_data_structures",
797 - "rustc-hash",
798 -]
799 -
800 -[[package]]
801 -name = "oxc_ast"
802 -version = "0.121.0"
803 -source = "registry+https://github.com/rust-lang/crates.io-index"
804 -checksum = "06ec0e9560cce8917197c7b13be7288707177f48a6f0ca116d0b53689e18bbc3"
805 -dependencies = [
806 - "bitflags",
807 - "oxc_allocator",
808 - "oxc_ast_macros",
809 - "oxc_data_structures",
810 - "oxc_diagnostics",
811 - "oxc_estree",
812 - "oxc_regular_expression",
813 - "oxc_span",
814 - "oxc_syntax",
815 -]
816 -
817 -[[package]]
818 -name = "oxc_ast_macros"
819 -version = "0.121.0"
820 -source = "registry+https://github.com/rust-lang/crates.io-index"
821 -checksum = "f266c05258e76cb84d7eee538e4fc75e2687f4220e1b2f141c490b35025a6443"
822 -dependencies = [
823 - "phf 0.13.1",
824 - "proc-macro2",
825 - "quote",
826 - "syn",
827 -]
828 -
829 -[[package]]
830 -name = "oxc_ast_visit"
831 -version = "0.121.0"
832 -source = "registry+https://github.com/rust-lang/crates.io-index"
833 -checksum = "3477ca0b6dd5bebcb1d3bf4c825b65999975d6ca91d6f535bf067e979fad113a"
834 -dependencies = [
835 - "oxc_allocator",
836 - "oxc_ast",
837 - "oxc_span",
838 - "oxc_syntax",
839 -]
840 -
841 -[[package]]
842 -name = "oxc_codegen"
843 -version = "0.121.0"
844 -source = "registry+https://github.com/rust-lang/crates.io-index"
845 -checksum = "a8b9da0a190c379ff816917b25338c4a47e9ed00201c67c209db5d4cca71a81c"
846 -dependencies = [
847 - "bitflags",
848 - "cow-utils",
849 - "dragonbox_ecma",
850 - "itoa",
851 - "oxc_allocator",
852 - "oxc_ast",
853 - "oxc_data_structures",
854 - "oxc_index",
855 - "oxc_semantic",
856 - "oxc_sourcemap",
857 - "oxc_span",
858 - "oxc_syntax",
859 - "rustc-hash",
860 -]
861 -
862 -[[package]]
863 -name = "oxc_data_structures"
864 -version = "0.121.0"
865 -source = "registry+https://github.com/rust-lang/crates.io-index"
866 -checksum = "d8701946f2acbd655610a331cf56f0aa58349ef792e6bf2fb65c56785b87fe8e"
867 -
868 -[[package]]
869 -name = "oxc_diagnostics"
870 -version = "0.121.0"
871 -source = "registry+https://github.com/rust-lang/crates.io-index"
872 -checksum = "b04ea16e6016eceb281fb61bbac5f860f075864e93ae15ec18b6c2d0b152e435"
873 -dependencies = [
874 - "cow-utils",
875 - "oxc-miette",
876 - "percent-encoding",
877 -]
878 -
879 -[[package]]
880 -name = "oxc_ecmascript"
881 -version = "0.121.0"
882 -source = "registry+https://github.com/rust-lang/crates.io-index"
883 -checksum = "f4b107cae9b8bce541a45463623e1c4b1bb073e81d966483720f0e831facdcb1"
884 -dependencies = [
885 - "cow-utils",
886 - "num-bigint",
887 - "num-traits",
888 - "oxc_allocator",
889 - "oxc_ast",
890 - "oxc_regular_expression",
891 - "oxc_span",
892 - "oxc_syntax",
893 -]
894 -
895 -[[package]]
896 -name = "oxc_estree"
897 -version = "0.121.0"
898 -source = "registry+https://github.com/rust-lang/crates.io-index"
899 -checksum = "57b79c9e9684eab83293d67dcbbfd2b1a1f062d27a8188411eb700c6e17983fa"
900 -
901 -[[package]]
902 -name = "oxc_index"
903 -version = "4.1.0"
904 -source = "registry+https://github.com/rust-lang/crates.io-index"
905 -checksum = "eb3e6120999627ec9703025eab7c9f410ebb7e95557632a8902ca48210416c2b"
906 -dependencies = [
907 - "nonmax",
908 - "serde",
909 -]
910 -
911 -[[package]]
912 -name = "oxc_parser"
913 -version = "0.121.0"
914 -source = "registry+https://github.com/rust-lang/crates.io-index"
915 -checksum = "487f41bdacb3ef9afd8c5b0cb5beceec3ac4ecd0c348804aa1907606d370c731"
916 -dependencies = [
917 - "bitflags",
918 - "cow-utils",
919 - "memchr",
920 - "num-bigint",
921 - "num-traits",
922 - "oxc_allocator",
923 - "oxc_ast",
924 - "oxc_data_structures",
925 - "oxc_diagnostics",
926 - "oxc_ecmascript",
927 - "oxc_regular_expression",
928 - "oxc_span",
929 - "oxc_syntax",
930 - "rustc-hash",
931 - "seq-macro",
932 -]
933 -
934 -[[package]]
935 -name = "oxc_regular_expression"
936 -version = "0.121.0"
937 -source = "registry+https://github.com/rust-lang/crates.io-index"
938 -checksum = "d495c085efbde1d65636497f9d3e3e58151db614a97e313e2e7a837d81865419"
939 -dependencies = [
940 - "bitflags",
941 - "oxc_allocator",
942 - "oxc_ast_macros",
943 - "oxc_diagnostics",
944 - "oxc_span",
945 - "phf 0.13.1",
946 - "rustc-hash",
947 - "unicode-id-start",
948 -]
949 -
950 -[[package]]
951 -name = "oxc_semantic"
952 -version = "0.121.0"
953 -source = "registry+https://github.com/rust-lang/crates.io-index"
954 -checksum = "6ac7034e3d2f5a73b39b5a0873bb3d38a504657c95cd1a8682b0d424a4bd3b77"
955 -dependencies = [
956 - "itertools",
957 - "memchr",
958 - "oxc_allocator",
959 - "oxc_ast",
960 - "oxc_ast_visit",
961 - "oxc_diagnostics",
962 - "oxc_ecmascript",
963 - "oxc_index",
964 - "oxc_span",
965 - "oxc_syntax",
966 - "rustc-hash",
967 - "self_cell",
968 -]
969 -
970 -[[package]]
971 -name = "oxc_sourcemap"
972 -version = "6.1.0"
973 -source = "registry+https://github.com/rust-lang/crates.io-index"
974 -checksum = "7cd10919ee3316ed4beef8b22b326b73b96c06029b0bff984a848269bb42a286"
975 -dependencies = [
976 - "base64-simd",
977 - "json-escape-simd",
978 - "rustc-hash",
979 - "serde",
980 - "serde_json",
981 -]
982 -
983 -[[package]]
984 -name = "oxc_span"
985 -version = "0.121.0"
986 -source = "registry+https://github.com/rust-lang/crates.io-index"
987 -checksum = "3edcf2bc8bc73cd8d252650737ef48a482484a91709b7f7a5c5ce49305f247e8"
988 -dependencies = [
989 - "compact_str 0.9.0",
990 - "oxc-miette",
991 - "oxc_allocator",
992 - "oxc_ast_macros",
993 - "oxc_estree",
994 - "oxc_str",
995 -]
996 -
997 -[[package]]
998 -name = "oxc_str"
999 -version = "0.121.0"
1000 -source = "registry+https://github.com/rust-lang/crates.io-index"
1001 -checksum = "a0c60f1570f04257d5678a16391f6d18dc805325e7f876b8e176a3a36fe897be"
1002 -dependencies = [
1003 - "compact_str 0.9.0",
1004 - "hashbrown 0.16.1",
1005 - "oxc_allocator",
1006 - "oxc_estree",
1007 -]
1008 -
1009 -[[package]]
1010 -name = "oxc_syntax"
1011 -version = "0.121.0"
1012 -source = "registry+https://github.com/rust-lang/crates.io-index"
1013 -checksum = "5a10c19c89298c0b126d12c5f545786405efbad9012d956ebb3190b64b29905a"
1014 -dependencies = [
1015 - "bitflags",
1016 - "cow-utils",
1017 - "dragonbox_ecma",
1018 - "nonmax",
1019 - "oxc_allocator",
1020 - "oxc_ast_macros",
1021 - "oxc_estree",
1022 - "oxc_index",
1023 - "oxc_span",
1024 - "phf 0.13.1",
1025 - "unicode-id-start",
1026 -]
1027 -
1028 -[[package]]
1029 -name = "percent-encoding"
1030 -version = "2.3.2"
1031 -source = "registry+https://github.com/rust-lang/crates.io-index"
1032 -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1033 -
1034 -[[package]]
1035 -name = "phf"
1036 -version = "0.11.3"
1037 -source = "registry+https://github.com/rust-lang/crates.io-index"
1038 -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
1039 -dependencies = [
1040 - "phf_macros 0.11.3",
1041 - "phf_shared 0.11.3",
1042 -]
1043 -
1044 -[[package]]
1045 -name = "phf"
1046 -version = "0.13.1"
1047 -source = "registry+https://github.com/rust-lang/crates.io-index"
1048 -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
1049 -dependencies = [
1050 - "phf_macros 0.13.1",
1051 - "phf_shared 0.13.1",
1052 - "serde",
1053 -]
1054 -
1055 -[[package]]
1056 -name = "phf_generator"
1057 -version = "0.11.3"
1058 -source = "registry+https://github.com/rust-lang/crates.io-index"
1059 -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
1060 -dependencies = [
1061 - "phf_shared 0.11.3",
1062 - "rand",
1063 -]
1064 -
1065 -[[package]]
1066 -name = "phf_generator"
1067 -version = "0.13.1"
1068 -source = "registry+https://github.com/rust-lang/crates.io-index"
1069 -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
1070 -dependencies = [
1071 - "fastrand",
1072 - "phf_shared 0.13.1",
1073 -]
1074 -
1075 -[[package]]
1076 -name = "phf_macros"
1077 -version = "0.11.3"
1078 -source = "registry+https://github.com/rust-lang/crates.io-index"
1079 -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
1080 -dependencies = [
1081 - "phf_generator 0.11.3",
1082 - "phf_shared 0.11.3",
1083 - "proc-macro2",
1084 - "quote",
1085 - "syn",
1086 -]
1087 -
1088 -[[package]]
1089 -name = "phf_macros"
1090 -version = "0.13.1"
1091 -source = "registry+https://github.com/rust-lang/crates.io-index"
1092 -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
1093 -dependencies = [
1094 - "phf_generator 0.13.1",
1095 - "phf_shared 0.13.1",
1096 - "proc-macro2",
1097 - "quote",
1098 - "syn",
1099 -]
1100 -
1101 -[[package]]
1102 -name = "phf_shared"
1103 -version = "0.11.3"
1104 -source = "registry+https://github.com/rust-lang/crates.io-index"
1105 -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
1106 -dependencies = [
1107 - "siphasher 1.0.2",
1108 -]
1109 -
1110 -[[package]]
1111 -name = "phf_shared"
1112 -version = "0.13.1"
1113 -source = "registry+https://github.com/rust-lang/crates.io-index"
1114 -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
1115 -dependencies = [
1116 - "siphasher 1.0.2",
1117 -]
1118 -
1119 -[[package]]
1120 -name = "pin-project-lite"
1121 -version = "0.2.17"
1122 -source = "registry+https://github.com/rust-lang/crates.io-index"
1123 -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
1124 -
1125 -[[package]]
1126 -name = "potential_utf"
1127 -version = "0.1.4"
1128 -source = "registry+https://github.com/rust-lang/crates.io-index"
1129 -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
1130 -dependencies = [
1131 - "zerovec",
1132 -]
1133 -
1134 -[[package]]
1135 -name = "proc-macro2"
1136 -version = "1.0.106"
1137 -source = "registry+https://github.com/rust-lang/crates.io-index"
1138 -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
1139 -dependencies = [
1140 - "unicode-ident",
1141 -]
1142 -
1143 -[[package]]
1144 -name = "psm"
1145 -version = "0.1.30"
1146 -source = "registry+https://github.com/rust-lang/crates.io-index"
1147 -checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
1148 -dependencies = [
1149 - "ar_archive_writer",
1150 - "cc",
1151 -]
1152 -
1153 -[[package]]
1154 -name = "quote"
1155 -version = "1.0.45"
228 +name = "quote"
229 +version = "1.0.45"
230 source = "registry+https://github.com/rust-lang/crates.io-index"
231 checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
232 dependencies = [
233 "proc-macro2",
234 ]
235
1162 -[[package]]
1163 -name = "rand"
1164 -version = "0.8.5"
1165 -source = "registry+https://github.com/rust-lang/crates.io-index"
1166 -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1167 -dependencies = [
1168 - "rand_core",
1169 -]
1170 -
1171 -[[package]]
1172 -name = "rand_core"
1173 -version = "0.6.4"
1174 -source = "registry+https://github.com/rust-lang/crates.io-index"
1175 -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1176 -
236 [[package]]
237 name = "react_compiler"
238 version = "0.1.0"
@@ -1212,27 +271,6 @@ dependencies = [
271 "serde",
272 ]
273
1215 -[[package]]
1216 -name = "react_compiler_e2e_cli"
1217 -version = "0.1.0"
1218 -dependencies = [
1219 - "clap",
1220 - "oxc_allocator",
1221 - "oxc_codegen",
1222 - "oxc_diagnostics",
1223 - "oxc_parser",
1224 - "oxc_semantic",
1225 - "oxc_span",
1226 - "react_compiler",
1227 - "react_compiler_ast",
1228 - "react_compiler_oxc",
1229 - "react_compiler_swc",
1230 - "serde_json",
1231 - "swc_common",
1232 - "swc_ecma_ast",
1233 - "swc_ecma_parser",
1234 -]
1235 -
274 [[package]]
275 name = "react_compiler_hir"
276 version = "0.1.0"
@@ -1291,549 +329,131 @@ dependencies = [
329 "react_compiler_ssa",
330 ]
331
1294 -[[package]]
1295 -name = "react_compiler_oxc"
1296 -version = "0.1.0"
1297 -dependencies = [
1298 - "indexmap",
1299 - "oxc_allocator",
1300 - "oxc_ast",
1301 - "oxc_ast_visit",
1302 - "oxc_codegen",
1303 - "oxc_diagnostics",
1304 - "oxc_parser",
1305 - "oxc_semantic",
1306 - "oxc_span",
1307 - "oxc_syntax",
1308 - "react_compiler",
1309 - "react_compiler_ast",
1310 - "react_compiler_diagnostics",
1311 - "react_compiler_hir",
1312 - "serde",
1313 - "serde_json",
1314 -]
1315 -
332 [[package]]
333 name = "react_compiler_reactive_scopes"
1318 -version = "0.1.0"
1319 -dependencies = [
1320 - "hmac",
1321 - "indexmap",
1322 - "react_compiler_ast",
1323 - "react_compiler_diagnostics",
1324 - "react_compiler_hir",
1325 - "serde_json",
1326 - "sha2",
1327 -]
1328 -
1329 -[[package]]
1330 -name = "react_compiler_ssa"
1331 -version = "0.1.0"
1332 -dependencies = [
1333 - "indexmap",
1334 - "react_compiler_diagnostics",
1335 - "react_compiler_hir",
1336 - "react_compiler_lowering",
1337 -]
1338 -
1339 -[[package]]
1340 -name = "react_compiler_swc"
1341 -version = "0.1.0"
1342 -dependencies = [
1343 - "indexmap",
1344 - "react_compiler",
1345 - "react_compiler_ast",
1346 - "react_compiler_diagnostics",
1347 - "react_compiler_hir",
1348 - "serde",
1349 - "serde_json",
1350 - "swc_atoms",
1351 - "swc_common",
1352 - "swc_ecma_ast",
1353 - "swc_ecma_codegen",
1354 - "swc_ecma_parser",
1355 - "swc_ecma_visit",
1356 -]
1357 -
1358 -[[package]]
1359 -name = "react_compiler_typeinference"
1360 -version = "0.1.0"
1361 -dependencies = [
1362 - "react_compiler_diagnostics",
1363 - "react_compiler_hir",
1364 - "react_compiler_ssa",
1365 -]
1366 -
1367 -[[package]]
1368 -name = "react_compiler_utils"
1369 -version = "0.1.0"
1370 -dependencies = [
1371 - "indexmap",
1372 -]
1373 -
1374 -[[package]]
1375 -name = "react_compiler_validation"
1376 -version = "0.1.0"
1377 -dependencies = [
1378 - "indexmap",
1379 - "react_compiler_diagnostics",
1380 - "react_compiler_hir",
1381 -]
1382 -
1383 -[[package]]
1384 -name = "regex"
1385 -version = "1.12.3"
1386 -source = "registry+https://github.com/rust-lang/crates.io-index"
1387 -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
1388 -dependencies = [
1389 - "aho-corasick",
1390 - "memchr",
1391 - "regex-automata",
1392 - "regex-syntax",
1393 -]
1394 -
1395 -[[package]]
1396 -name = "regex-automata"
1397 -version = "0.4.14"
1398 -source = "registry+https://github.com/rust-lang/crates.io-index"
1399 -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1400 -dependencies = [
1401 - "aho-corasick",
1402 - "memchr",
1403 - "regex-syntax",
1404 -]
1405 -
1406 -[[package]]
1407 -name = "regex-syntax"
1408 -version = "0.8.10"
1409 -source = "registry+https://github.com/rust-lang/crates.io-index"
1410 -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
1411 -
1412 -[[package]]
1413 -name = "rustc-hash"
1414 -version = "2.1.1"
1415 -source = "registry+https://github.com/rust-lang/crates.io-index"
1416 -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
1417 -
1418 -[[package]]
1419 -name = "rustversion"
1420 -version = "1.0.22"
1421 -source = "registry+https://github.com/rust-lang/crates.io-index"
1422 -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1423 -
1424 -[[package]]
1425 -name = "ryu"
1426 -version = "1.0.23"
1427 -source = "registry+https://github.com/rust-lang/crates.io-index"
1428 -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1429 -
1430 -[[package]]
1431 -name = "same-file"
1432 -version = "1.0.6"
1433 -source = "registry+https://github.com/rust-lang/crates.io-index"
1434 -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1435 -dependencies = [
1436 - "winapi-util",
1437 -]
1438 -
1439 -[[package]]
1440 -name = "scoped-tls"
1441 -version = "1.0.1"
1442 -source = "registry+https://github.com/rust-lang/crates.io-index"
1443 -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
1444 -
1445 -[[package]]
1446 -name = "self_cell"
1447 -version = "1.2.2"
1448 -source = "registry+https://github.com/rust-lang/crates.io-index"
1449 -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
1450 -
1451 -[[package]]
1452 -name = "semver"
1453 -version = "1.0.27"
1454 -source = "registry+https://github.com/rust-lang/crates.io-index"
1455 -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
1456 -
1457 -[[package]]
1458 -name = "seq-macro"
1459 -version = "0.3.6"
1460 -source = "registry+https://github.com/rust-lang/crates.io-index"
1461 -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
1462 -
1463 -[[package]]
1464 -name = "serde"
1465 -version = "1.0.228"
1466 -source = "registry+https://github.com/rust-lang/crates.io-index"
1467 -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1468 -dependencies = [
1469 - "serde_core",
1470 - "serde_derive",
1471 -]
1472 -
1473 -[[package]]
1474 -name = "serde-transcode"
1475 -version = "1.1.1"
1476 -source = "registry+https://github.com/rust-lang/crates.io-index"
1477 -checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2"
1478 -dependencies = [
1479 - "serde",
1480 -]
1481 -
1482 -[[package]]
1483 -name = "serde_core"
1484 -version = "1.0.228"
1485 -source = "registry+https://github.com/rust-lang/crates.io-index"
1486 -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1487 -dependencies = [
1488 - "serde_derive",
1489 -]
1490 -
1491 -[[package]]
1492 -name = "serde_derive"
1493 -version = "1.0.228"
1494 -source = "registry+https://github.com/rust-lang/crates.io-index"
1495 -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1496 -dependencies = [
1497 - "proc-macro2",
1498 - "quote",
1499 - "syn",
1500 -]
1501 -
1502 -[[package]]
1503 -name = "serde_json"
1504 -version = "1.0.149"
1505 -source = "registry+https://github.com/rust-lang/crates.io-index"
1506 -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
1507 -dependencies = [
1508 - "itoa",
1509 - "memchr",
1510 - "serde",
1511 - "serde_core",
1512 - "zmij",
1513 -]
1514 -
1515 -[[package]]
1516 -name = "sha2"
1517 -version = "0.10.9"
1518 -source = "registry+https://github.com/rust-lang/crates.io-index"
1519 -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
1520 -dependencies = [
1521 - "cfg-if",
1522 - "cpufeatures",
1523 - "digest",
1524 -]
1525 -
1526 -[[package]]
1527 -name = "shlex"
1528 -version = "1.3.0"
1529 -source = "registry+https://github.com/rust-lang/crates.io-index"
1530 -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1531 -
1532 -[[package]]
1533 -name = "similar"
1534 -version = "2.7.0"
1535 -source = "registry+https://github.com/rust-lang/crates.io-index"
1536 -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
1537 -
1538 -[[package]]
1539 -name = "siphasher"
1540 -version = "0.3.11"
1541 -source = "registry+https://github.com/rust-lang/crates.io-index"
1542 -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
1543 -
1544 -[[package]]
1545 -name = "siphasher"
1546 -version = "1.0.2"
1547 -source = "registry+https://github.com/rust-lang/crates.io-index"
1548 -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
1549 -
1550 -[[package]]
1551 -name = "smallvec"
1552 -version = "1.15.1"
1553 -source = "registry+https://github.com/rust-lang/crates.io-index"
1554 -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1555 -
1556 -[[package]]
1557 -name = "smartstring"
1558 -version = "1.0.1"
1559 -source = "registry+https://github.com/rust-lang/crates.io-index"
1560 -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
1561 -dependencies = [
1562 - "autocfg",
1563 - "static_assertions",
1564 - "version_check",
1565 -]
1566 -
1567 -[[package]]
1568 -name = "smawk"
1569 -version = "0.3.2"
1570 -source = "registry+https://github.com/rust-lang/crates.io-index"
1571 -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
1572 -
1573 -[[package]]
1574 -name = "stable_deref_trait"
1575 -version = "1.2.1"
1576 -source = "registry+https://github.com/rust-lang/crates.io-index"
1577 -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1578 -
1579 -[[package]]
1580 -name = "stacker"
1581 -version = "0.1.23"
1582 -source = "registry+https://github.com/rust-lang/crates.io-index"
1583 -checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
1584 -dependencies = [
1585 - "cc",
1586 - "cfg-if",
1587 - "libc",
1588 - "psm",
1589 - "windows-sys 0.59.0",
1590 -]
1591 -
1592 -[[package]]
1593 -name = "static_assertions"
1594 -version = "1.1.0"
1595 -source = "registry+https://github.com/rust-lang/crates.io-index"
1596 -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1597 -
1598 -[[package]]
1599 -name = "string_enum"
1600 -version = "1.0.2"
1601 -source = "registry+https://github.com/rust-lang/crates.io-index"
1602 -checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e"
1603 -dependencies = [
1604 - "quote",
1605 - "swc_macros_common",
1606 - "syn",
1607 -]
1608 -
1609 -[[package]]
1610 -name = "strsim"
1611 -version = "0.11.1"
1612 -source = "registry+https://github.com/rust-lang/crates.io-index"
1613 -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1614 -
1615 -[[package]]
1616 -name = "subtle"
1617 -version = "2.6.1"
1618 -source = "registry+https://github.com/rust-lang/crates.io-index"
1619 -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1620 -
1621 -[[package]]
1622 -name = "swc_allocator"
1623 -version = "4.0.1"
1624 -source = "registry+https://github.com/rust-lang/crates.io-index"
1625 -checksum = "9d7eefd2c8b228a8c73056482b2ae4b3a1071fbe07638e3b55ceca8570cc48bb"
1626 -dependencies = [
1627 - "allocator-api2",
1628 - "bumpalo",
1629 - "hashbrown 0.14.5",
1630 - "rustc-hash",
1631 -]
1632 -
1633 -[[package]]
1634 -name = "swc_atoms"
1635 -version = "9.0.0"
1636 -source = "registry+https://github.com/rust-lang/crates.io-index"
1637 -checksum = "d4ccbe2ecad10ad7432100f878a107b1d972a8aee83ca53184d00c23a078bb8a"
1638 -dependencies = [
1639 - "hstr",
1640 - "once_cell",
1641 - "serde",
1642 -]
1643 -
1644 -[[package]]
1645 -name = "swc_common"
1646 -version = "19.0.0"
1647 -source = "registry+https://github.com/rust-lang/crates.io-index"
1648 -checksum = "623a4ee8bb19d87de6fc781e44e1696af20136d1c1eabf9f3712ff1fb50b6189"
334 +version = "0.1.0"
335 dependencies = [
1650 - "anyhow",
1651 - "ast_node",
1652 - "better_scoped_tls",
1653 - "bytes-str",
1654 - "either",
1655 - "from_variant",
1656 - "num-bigint",
1657 - "once_cell",
1658 - "rustc-hash",
1659 - "serde",
1660 - "siphasher 0.3.11",
1661 - "swc_atoms",
1662 - "swc_eq_ignore_macros",
1663 - "swc_visit",
1664 - "tracing",
1665 - "unicode-width",
1666 - "url",
336 + "hmac",
337 + "indexmap",
338 + "react_compiler_ast",
339 + "react_compiler_diagnostics",
340 + "react_compiler_hir",
341 + "serde_json",
342 + "sha2",
343 ]
344
345 [[package]]
1670 -name = "swc_ecma_ast"
1671 -version = "21.0.0"
1672 -source = "registry+https://github.com/rust-lang/crates.io-index"
1673 -checksum = "27111582629a1cc116f9cffa6bfa501e6c849e0e66fafdf78cd404dce919117d"
346 +name = "react_compiler_ssa"
347 +version = "0.1.0"
348 dependencies = [
1675 - "bitflags",
1676 - "is-macro",
1677 - "num-bigint",
1678 - "once_cell",
1679 - "phf 0.11.3",
1680 - "rustc-hash",
1681 - "string_enum",
1682 - "swc_atoms",
1683 - "swc_common",
1684 - "swc_visit",
1685 - "unicode-id-start",
349 + "indexmap",
350 + "react_compiler_diagnostics",
351 + "react_compiler_hir",
352 + "react_compiler_lowering",
353 ]
354
355 [[package]]
1689 -name = "swc_ecma_codegen"
1690 -version = "24.0.0"
1691 -source = "registry+https://github.com/rust-lang/crates.io-index"
1692 -checksum = "50b8dbdc2be434883934cda8c3f6638130390032c44e1952e543252fcafd67e0"
356 +name = "react_compiler_typeinference"
357 +version = "0.1.0"
358 dependencies = [
1694 - "ascii",
1695 - "compact_str 0.7.1",
1696 - "dragonbox_ecma",
1697 - "memchr",
1698 - "num-bigint",
1699 - "once_cell",
1700 - "regex",
1701 - "rustc-hash",
1702 - "serde",
1703 - "swc_allocator",
1704 - "swc_atoms",
1705 - "swc_common",
1706 - "swc_ecma_ast",
1707 - "swc_ecma_codegen_macros",
1708 - "tracing",
359 + "react_compiler_diagnostics",
360 + "react_compiler_hir",
361 + "react_compiler_ssa",
362 ]
363
364 [[package]]
1712 -name = "swc_ecma_codegen_macros"
1713 -version = "2.0.2"
1714 -source = "registry+https://github.com/rust-lang/crates.io-index"
1715 -checksum = "e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8"
365 +name = "react_compiler_utils"
366 +version = "0.1.0"
367 dependencies = [
1717 - "proc-macro2",
1718 - "swc_macros_common",
1719 - "syn",
368 + "indexmap",
369 ]
370
371 [[package]]
1723 -name = "swc_ecma_parser"
1724 -version = "35.0.0"
1725 -source = "registry+https://github.com/rust-lang/crates.io-index"
1726 -checksum = "943b8743c57783b35b6c173b0a8ef539a6c1d06ee5d1588b2821992c3fd35f39"
372 +name = "react_compiler_validation"
373 +version = "0.1.0"
374 dependencies = [
1728 - "bitflags",
1729 - "either",
1730 - "num-bigint",
1731 - "phf 0.11.3",
1732 - "rustc-hash",
1733 - "seq-macro",
1734 - "serde",
1735 - "smartstring",
1736 - "stacker",
1737 - "swc_atoms",
1738 - "swc_common",
1739 - "swc_ecma_ast",
1740 - "tracing",
375 + "indexmap",
376 + "react_compiler_diagnostics",
377 + "react_compiler_hir",
378 ]
379
380 [[package]]
1744 -name = "swc_ecma_visit"
1745 -version = "21.0.0"
381 +name = "regex"
382 +version = "1.12.3"
383 source = "registry+https://github.com/rust-lang/crates.io-index"
1747 -checksum = "7c1b3a04c999c14f09d81c959f8a84f71d594f2ad2456470eb38d78532e82dda"
384 +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
385 dependencies = [
1749 - "new_debug_unreachable",
1750 - "num-bigint",
1751 - "swc_atoms",
1752 - "swc_common",
1753 - "swc_ecma_ast",
1754 - "swc_visit",
1755 - "tracing",
386 + "aho-corasick",
387 + "memchr",
388 + "regex-automata",
389 + "regex-syntax",
390 ]
391
392 [[package]]
1759 -name = "swc_eq_ignore_macros"
1760 -version = "1.0.1"
393 +name = "regex-automata"
394 +version = "0.4.14"
395 source = "registry+https://github.com/rust-lang/crates.io-index"
1762 -checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632"
396 +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
397 dependencies = [
1764 - "proc-macro2",
1765 - "quote",
1766 - "syn",
398 + "aho-corasick",
399 + "memchr",
400 + "regex-syntax",
401 ]
402
403 [[package]]
1770 -name = "swc_macros_common"
1771 -version = "1.0.1"
404 +name = "regex-syntax"
405 +version = "0.8.10"
406 source = "registry+https://github.com/rust-lang/crates.io-index"
1773 -checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae"
1774 -dependencies = [
1775 - "proc-macro2",
1776 - "quote",
1777 - "syn",
1778 -]
407 +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
408
409 [[package]]
1781 -name = "swc_visit"
1782 -version = "2.0.1"
410 +name = "same-file"
411 +version = "1.0.6"
412 source = "registry+https://github.com/rust-lang/crates.io-index"
1784 -checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2"
413 +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
414 dependencies = [
1786 - "either",
1787 - "new_debug_unreachable",
415 + "winapi-util",
416 ]
417
418 [[package]]
1791 -name = "syn"
1792 -version = "2.0.117"
419 +name = "semver"
420 +version = "1.0.27"
421 source = "registry+https://github.com/rust-lang/crates.io-index"
1794 -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1795 -dependencies = [
1796 - "proc-macro2",
1797 - "quote",
1798 - "unicode-ident",
1799 -]
422 +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
423
424 [[package]]
1802 -name = "synstructure"
1803 -version = "0.13.2"
425 +name = "serde"
426 +version = "1.0.228"
427 source = "registry+https://github.com/rust-lang/crates.io-index"
1805 -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
428 +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
429 dependencies = [
1807 - "proc-macro2",
1808 - "quote",
1809 - "syn",
430 + "serde_core",
431 + "serde_derive",
432 ]
433
434 [[package]]
1813 -name = "textwrap"
1814 -version = "0.16.2"
435 +name = "serde-transcode"
436 +version = "1.1.1"
437 source = "registry+https://github.com/rust-lang/crates.io-index"
1816 -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
438 +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2"
439 dependencies = [
1818 - "smawk",
1819 - "unicode-linebreak",
1820 - "unicode-width",
440 + "serde",
441 ]
442
443 [[package]]
1824 -name = "thiserror"
1825 -version = "2.0.18"
444 +name = "serde_core"
445 +version = "1.0.228"
446 source = "registry+https://github.com/rust-lang/crates.io-index"
1827 -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
447 +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
448 dependencies = [
1829 - "thiserror-impl",
449 + "serde_derive",
450 ]
451
452 [[package]]
1833 -name = "thiserror-impl"
1834 -version = "2.0.18"
453 +name = "serde_derive"
454 +version = "1.0.228"
455 source = "registry+https://github.com/rust-lang/crates.io-index"
1836 -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
456 +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
457 dependencies = [
458 "proc-macro2",
459 "quote",
@@ -1841,54 +461,50 @@ dependencies = [
461 ]
462
463 [[package]]
1844 -name = "tinystr"
1845 -version = "0.8.2"
464 +name = "serde_json"
465 +version = "1.0.149"
466 source = "registry+https://github.com/rust-lang/crates.io-index"
1847 -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
467 +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
468 dependencies = [
1849 - "displaydoc",
1850 - "zerovec",
469 + "itoa",
470 + "memchr",
471 + "serde",
472 + "serde_core",
473 + "zmij",
474 ]
475
476 [[package]]
1854 -name = "tracing"
1855 -version = "0.1.44"
477 +name = "sha2"
478 +version = "0.10.9"
479 source = "registry+https://github.com/rust-lang/crates.io-index"
1857 -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
480 +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
481 dependencies = [
1859 - "pin-project-lite",
1860 - "tracing-attributes",
1861 - "tracing-core",
482 + "cfg-if",
483 + "cpufeatures",
484 + "digest",
485 ]
486
487 [[package]]
1865 -name = "tracing-attributes"
1866 -version = "0.1.31"
488 +name = "similar"
489 +version = "2.7.0"
490 source = "registry+https://github.com/rust-lang/crates.io-index"
1868 -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
1869 -dependencies = [
1870 - "proc-macro2",
1871 - "quote",
1872 - "syn",
1873 -]
491 +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
492
493 [[package]]
1876 -name = "tracing-core"
1877 -version = "0.1.36"
494 +name = "subtle"
495 +version = "2.6.1"
496 source = "registry+https://github.com/rust-lang/crates.io-index"
1879 -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
1880 -dependencies = [
1881 - "once_cell",
1882 -]
497 +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
498
499 [[package]]
1885 -name = "triomphe"
1886 -version = "0.1.15"
500 +name = "syn"
501 +version = "2.0.117"
502 source = "registry+https://github.com/rust-lang/crates.io-index"
1888 -checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
503 +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
504 dependencies = [
1890 - "serde",
1891 - "stable_deref_trait",
505 + "proc-macro2",
506 + "quote",
507 + "unicode-ident",
508 ]
509
510 [[package]]
@@ -1897,72 +513,24 @@ version = "1.19.0"
513 source = "registry+https://github.com/rust-lang/crates.io-index"
514 checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
515
1900 -[[package]]
1901 -name = "unicode-id-start"
1902 -version = "1.4.0"
1903 -source = "registry+https://github.com/rust-lang/crates.io-index"
1904 -checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007"
1905 -
516 [[package]]
517 name = "unicode-ident"
518 version = "1.0.24"
519 source = "registry+https://github.com/rust-lang/crates.io-index"
520 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
521
1912 -[[package]]
1913 -name = "unicode-linebreak"
1914 -version = "0.1.5"
1915 -source = "registry+https://github.com/rust-lang/crates.io-index"
1916 -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
1917 -
522 [[package]]
523 name = "unicode-segmentation"
524 version = "1.12.0"
525 source = "registry+https://github.com/rust-lang/crates.io-index"
526 checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
527
1924 -[[package]]
1925 -name = "unicode-width"
1926 -version = "0.2.2"
1927 -source = "registry+https://github.com/rust-lang/crates.io-index"
1928 -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
1929 -
1930 -[[package]]
1931 -name = "url"
1932 -version = "2.5.8"
1933 -source = "registry+https://github.com/rust-lang/crates.io-index"
1934 -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
1935 -dependencies = [
1936 - "form_urlencoded",
1937 - "idna",
1938 - "percent-encoding",
1939 - "serde",
1940 -]
1941 -
1942 -[[package]]
1943 -name = "utf8_iter"
1944 -version = "1.0.4"
1945 -source = "registry+https://github.com/rust-lang/crates.io-index"
1946 -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
1947 -
1948 -[[package]]
1949 -name = "utf8parse"
1950 -version = "0.2.2"
1951 -source = "registry+https://github.com/rust-lang/crates.io-index"
1952 -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1953 -
528 [[package]]
529 name = "version_check"
530 version = "0.9.5"
531 source = "registry+https://github.com/rust-lang/crates.io-index"
532 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
533
1960 -[[package]]
1961 -name = "vsimd"
1962 -version = "0.8.0"
1963 -source = "registry+https://github.com/rust-lang/crates.io-index"
1964 -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
1965 -
534 [[package]]
535 name = "walkdir"
536 version = "2.5.0"
@@ -1979,7 +547,7 @@ version = "0.1.11"
547 source = "registry+https://github.com/rust-lang/crates.io-index"
548 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
549 dependencies = [
1982 - "windows-sys 0.61.2",
550 + "windows-sys",
551 ]
552
553 [[package]]
@@ -1988,15 +556,6 @@ version = "0.2.1"
556 source = "registry+https://github.com/rust-lang/crates.io-index"
557 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
558
1991 -[[package]]
1992 -name = "windows-sys"
1993 -version = "0.59.0"
1994 -source = "registry+https://github.com/rust-lang/crates.io-index"
1995 -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1996 -dependencies = [
1997 - "windows-targets",
1998 -]
1999 -
559 [[package]]
560 name = "windows-sys"
561 version = "0.61.2"
@@ -2006,173 +565,6 @@ dependencies = [
565 "windows-link",
566 ]
567
2009 -[[package]]
2010 -name = "windows-targets"
2011 -version = "0.52.6"
2012 -source = "registry+https://github.com/rust-lang/crates.io-index"
2013 -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2014 -dependencies = [
2015 - "windows_aarch64_gnullvm",
2016 - "windows_aarch64_msvc",
2017 - "windows_i686_gnu",
2018 - "windows_i686_gnullvm",
2019 - "windows_i686_msvc",
2020 - "windows_x86_64_gnu",
2021 - "windows_x86_64_gnullvm",
2022 - "windows_x86_64_msvc",
2023 -]
2024 -
2025 -[[package]]
2026 -name = "windows_aarch64_gnullvm"
2027 -version = "0.52.6"
2028 -source = "registry+https://github.com/rust-lang/crates.io-index"
2029 -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2030 -
2031 -[[package]]
2032 -name = "windows_aarch64_msvc"
2033 -version = "0.52.6"
2034 -source = "registry+https://github.com/rust-lang/crates.io-index"
2035 -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2036 -
2037 -[[package]]
2038 -name = "windows_i686_gnu"
2039 -version = "0.52.6"
2040 -source = "registry+https://github.com/rust-lang/crates.io-index"
2041 -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2042 -
2043 -[[package]]
2044 -name = "windows_i686_gnullvm"
2045 -version = "0.52.6"
2046 -source = "registry+https://github.com/rust-lang/crates.io-index"
2047 -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2048 -
2049 -[[package]]
2050 -name = "windows_i686_msvc"
2051 -version = "0.52.6"
2052 -source = "registry+https://github.com/rust-lang/crates.io-index"
2053 -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2054 -
2055 -[[package]]
2056 -name = "windows_x86_64_gnu"
2057 -version = "0.52.6"
2058 -source = "registry+https://github.com/rust-lang/crates.io-index"
2059 -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2060 -
2061 -[[package]]
2062 -name = "windows_x86_64_gnullvm"
2063 -version = "0.52.6"
2064 -source = "registry+https://github.com/rust-lang/crates.io-index"
2065 -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2066 -
2067 -[[package]]
2068 -name = "windows_x86_64_msvc"
2069 -version = "0.52.6"
2070 -source = "registry+https://github.com/rust-lang/crates.io-index"
2071 -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2072 -
2073 -[[package]]
2074 -name = "writeable"
2075 -version = "0.6.2"
2076 -source = "registry+https://github.com/rust-lang/crates.io-index"
2077 -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
2078 -
2079 -[[package]]
2080 -name = "yoke"
2081 -version = "0.8.1"
2082 -source = "registry+https://github.com/rust-lang/crates.io-index"
2083 -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
2084 -dependencies = [
2085 - "stable_deref_trait",
2086 - "yoke-derive",
2087 - "zerofrom",
2088 -]
2089 -
2090 -[[package]]
2091 -name = "yoke-derive"
2092 -version = "0.8.1"
2093 -source = "registry+https://github.com/rust-lang/crates.io-index"
2094 -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
2095 -dependencies = [
2096 - "proc-macro2",
2097 - "quote",
2098 - "syn",
2099 - "synstructure",
2100 -]
2101 -
2102 -[[package]]
2103 -name = "zerocopy"
2104 -version = "0.8.47"
2105 -source = "registry+https://github.com/rust-lang/crates.io-index"
2106 -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
2107 -dependencies = [
2108 - "zerocopy-derive",
2109 -]
2110 -
2111 -[[package]]
2112 -name = "zerocopy-derive"
2113 -version = "0.8.47"
2114 -source = "registry+https://github.com/rust-lang/crates.io-index"
2115 -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
2116 -dependencies = [
2117 - "proc-macro2",
2118 - "quote",
2119 - "syn",
2120 -]
2121 -
2122 -[[package]]
2123 -name = "zerofrom"
2124 -version = "0.1.6"
2125 -source = "registry+https://github.com/rust-lang/crates.io-index"
2126 -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
2127 -dependencies = [
2128 - "zerofrom-derive",
2129 -]
2130 -
2131 -[[package]]
2132 -name = "zerofrom-derive"
2133 -version = "0.1.6"
2134 -source = "registry+https://github.com/rust-lang/crates.io-index"
2135 -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
2136 -dependencies = [
2137 - "proc-macro2",
2138 - "quote",
2139 - "syn",
2140 - "synstructure",
2141 -]
2142 -
2143 -[[package]]
2144 -name = "zerotrie"
2145 -version = "0.2.3"
2146 -source = "registry+https://github.com/rust-lang/crates.io-index"
2147 -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
2148 -dependencies = [
2149 - "displaydoc",
2150 - "yoke",
2151 - "zerofrom",
2152 -]
2153 -
2154 -[[package]]
2155 -name = "zerovec"
2156 -version = "0.11.5"
2157 -source = "registry+https://github.com/rust-lang/crates.io-index"
2158 -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
2159 -dependencies = [
2160 - "yoke",
2161 - "zerofrom",
2162 - "zerovec-derive",
2163 -]
2164 -
2165 -[[package]]
2166 -name = "zerovec-derive"
2167 -version = "0.11.2"
2168 -source = "registry+https://github.com/rust-lang/crates.io-index"
2169 -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
2170 -dependencies = [
2171 - "proc-macro2",
2172 - "quote",
2173 - "syn",
2174 -]
2175 -
568 [[package]]
569 name = "zmij"
570 version = "1.0.21"
compiler/crates/react_compiler/src/debug_print.rs
+21 -17
@@ -43,7 +43,8 @@ impl<'a> DebugPrinter<'a> {
43 self.fmt.line(&format!("fn_type: {:?}", func.fn_type));
44 self.fmt.line(&format!("generator: {}", func.generator));
45 self.fmt.line(&format!("is_async: {}", func.is_async));
46 - self.fmt.line(&format!("loc: {}", print::format_loc(&func.loc)));
46 + self.fmt
47 + .line(&format!("loc: {}", print::format_loc(&func.loc)));
48
49 // params
50 self.fmt.line("params:");
@@ -83,7 +84,8 @@ impl<'a> DebugPrinter<'a> {
84 self.fmt.line("aliasingEffects:");
85 self.fmt.indent();
86 for (i, eff) in effects.iter().enumerate() {
86 - self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
87 + self.fmt
88 + .line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
89 }
90 self.fmt.dedent();
91 }
@@ -127,7 +129,8 @@ impl<'a> DebugPrinter<'a> {
129 block: &BasicBlock,
130 instructions: &[Instruction],
131 ) {
130 - self.fmt.line(&format!("bb{} ({}):", block_id.0, block.kind));
132 + self.fmt
133 + .line(&format!("bb{} ({}):", block_id.0, block.kind));
134 self.fmt.indent();
135
136 // preds
@@ -223,13 +226,15 @@ impl<'a> DebugPrinter<'a> {
226 self.fmt.line("effects:");
227 self.fmt.indent();
228 for (i, eff) in effects.iter().enumerate() {
226 - self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
229 + self.fmt
230 + .line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
231 }
232 self.fmt.dedent();
233 }
234 None => self.fmt.line("effects: null"),
235 }
232 - self.fmt.line(&format!("loc: {}", print::format_loc(&instr.loc)));
236 + self.fmt
237 + .line(&format!("loc: {}", print::format_loc(&instr.loc)));
238 self.fmt.dedent();
239 self.fmt.line("}");
240 }
@@ -346,14 +351,16 @@ impl<'a> DebugPrinter<'a> {
351 self.fmt.line("Return {");
352 self.fmt.indent();
353 self.fmt.line(&format!("id: {}", id.0));
349 - self.fmt.line(&format!("returnVariant: {:?}", return_variant));
354 + self.fmt
355 + .line(&format!("returnVariant: {:?}", return_variant));
356 self.fmt.format_place_field("value", value);
357 match effects {
358 Some(e) => {
359 self.fmt.line("effects:");
360 self.fmt.indent();
361 for (i, eff) in e.iter().enumerate() {
356 - self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
362 + self.fmt
363 + .line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
364 }
365 self.fmt.dedent();
366 }
@@ -402,10 +409,8 @@ impl<'a> DebugPrinter<'a> {
409 self.fmt.line("}");
410 }
411 None => {
405 - self.fmt.line(&format!(
406 - "[{}] Default {{ block: bb{} }}",
407 - i, case.block.0
408 - ));
412 + self.fmt
413 + .line(&format!("[{}] Default {{ block: bb{} }}", i, case.block.0));
414 }
415 }
416 }
@@ -566,7 +571,8 @@ impl<'a> DebugPrinter<'a> {
571 self.fmt.line("MaybeThrow {");
572 self.fmt.indent();
573 self.fmt.line(&format!("id: {}", id.0));
569 - self.fmt.line(&format!("continuation: bb{}", continuation.0));
574 + self.fmt
575 + .line(&format!("continuation: bb{}", continuation.0));
576 self.fmt.line(&format!(
577 "handler: {}",
578 match handler {
@@ -579,7 +585,8 @@ impl<'a> DebugPrinter<'a> {
585 self.fmt.line("effects:");
586 self.fmt.indent();
587 for (i, eff) in e.iter().enumerate() {
582 - self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
588 + self.fmt
589 + .line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
590 }
591 self.fmt.dedent();
592 }
@@ -686,10 +693,7 @@ pub fn format_errors(error: &CompilerError) -> String {
693 /// Format an HIR function into a reactive PrintFormatter.
694 /// This bridges the two debug printers so inner functions in FunctionExpression/ObjectMethod
695 /// can be printed within the reactive function output.
689 -pub fn format_hir_function_into(
690 - reactive_fmt: &mut PrintFormatter,
691 - func: &HirFunction,
692 -) {
696 +pub fn format_hir_function_into(reactive_fmt: &mut PrintFormatter, func: &HirFunction) {
697 // Create a temporary DebugPrinter that shares the same environment
698 let mut printer = DebugPrinter {
699 fmt: PrintFormatter {
compiler/crates/react_compiler/src/entrypoint/compile_result.rs
+7 -2
@@ -1,7 +1,7 @@
1 +use react_compiler_ast::File;
2 use react_compiler_ast::expressions::Identifier as AstIdentifier;
3 use react_compiler_ast::patterns::PatternLike;
4 use react_compiler_ast::statements::BlockStatement;
4 -use react_compiler_ast::File;
5 use react_compiler_diagnostics::SourceLocation;
6 use react_compiler_hir::ReactFunctionType;
7 use serde::Serialize;
@@ -30,7 +30,12 @@ pub struct LoggerPosition {
30
31 impl LoggerSourceLocation {
32 /// Create from a diagnostics SourceLocation, adding index and filename.
33 - pub fn from_loc(loc: &SourceLocation, filename: Option<&str>, start_index: Option<u32>, end_index: Option<u32>) -> Self {
33 + pub fn from_loc(
34 + loc: &SourceLocation,
35 + filename: Option<&str>,
36 + start_index: Option<u32>,
37 + end_index: Option<u32>,
38 + ) -> Self {
39 Self {
40 start: LoggerPosition {
41 line: loc.start.line,
compiler/crates/react_compiler/src/entrypoint/imports.rs
+19 -5
@@ -12,13 +12,17 @@ use react_compiler_ast::declarations::{
12 };
13 use react_compiler_ast::expressions::{CallExpression, Expression, Identifier};
14 use react_compiler_ast::literals::StringLiteral;
15 -use react_compiler_ast::patterns::{ObjectPattern, ObjectPatternProp, ObjectPatternProperty, PatternLike};
15 +use react_compiler_ast::patterns::{
16 + ObjectPattern, ObjectPatternProp, ObjectPatternProperty, PatternLike,
17 +};
18 use react_compiler_ast::scope::ScopeInfo;
19 use react_compiler_ast::statements::{
20 Statement, VariableDeclaration, VariableDeclarationKind, VariableDeclarator,
21 };
22 use react_compiler_ast::{Program, SourceType};
21 -use react_compiler_diagnostics::{CompilerError, CompilerErrorDetail, ErrorCategory, Position, SourceLocation};
23 +use react_compiler_diagnostics::{
24 + CompilerError, CompilerErrorDetail, ErrorCategory, Position, SourceLocation,
25 +};
26
27 use super::compile_result::{DebugLogEntry, LoggerEvent, OrderedLogItem};
28 use super::plugin_options::{CompilerTarget, PluginOptions};
@@ -238,7 +242,9 @@ impl ProgramContext {
242
243 /// Log a compilation event.
244 pub fn log_event(&mut self, event: LoggerEvent) {
241 - self.ordered_log.push(OrderedLogItem::Event { event: event.clone() });
245 + self.ordered_log.push(OrderedLogItem::Event {
246 + event: event.clone(),
247 + });
248 self.events.push(event);
249 }
250
@@ -280,8 +286,16 @@ pub fn validate_restricted_imports(
286 )
287 .with_description(format!("Import from module {}", import.source.value));
288 detail.loc = import.base.loc.as_ref().map(|loc| SourceLocation {
283 - start: Position { line: loc.start.line, column: loc.start.column, index: loc.start.index },
284 - end: Position { line: loc.end.line, column: loc.end.column, index: loc.end.index },
289 + start: Position {
290 + line: loc.start.line,
291 + column: loc.start.column,
292 + index: loc.start.index,
293 + },
294 + end: Position {
295 + line: loc.end.line,
296 + column: loc.end.column,
297 + index: loc.end.index,
298 + },
299 });
300 error.push_error_detail(detail);
301 }
compiler/crates/react_compiler/src/entrypoint/program.rs
+6 -7
@@ -235,9 +235,7 @@ fn find_directives_dynamic_gating<'a>(
235 /// `^use memo if\(([^\)]*)\)$`: the condition may not contain `)` and the
236 /// directive must end at the closing paren.
237 fn parse_dynamic_gating_directive(value: &str) -> Option<&str> {
238 - let condition = value
239 - .strip_prefix("use memo if(")?
240 - .strip_suffix(')')?;
238 + let condition = value.strip_prefix("use memo if(")?.strip_suffix(')')?;
239 if condition.contains(')') {
240 return None;
241 }
@@ -2198,11 +2196,12 @@ fn raw_node_references_identifier(value: &serde_json::Value, name: &str) -> bool
2196 {
2197 return true;
2198 }
2201 - map.values().any(|v| raw_node_references_identifier(v, name))
2202 - }
2203 - serde_json::Value::Array(items) => {
2204 - items.iter().any(|v| raw_node_references_identifier(v, name))
2199 + map.values()
2200 + .any(|v| raw_node_references_identifier(v, name))
2201 }
2202 + serde_json::Value::Array(items) => items
2203 + .iter()
2204 + .any(|v| raw_node_references_identifier(v, name)),
2205 _ => false,
2206 }
2207 }
compiler/crates/react_compiler/src/entrypoint/suppression.rs
+19 -10
@@ -38,12 +38,16 @@ fn comment_data(comment: &Comment) -> &CommentData {
38 /// Check if a comment value matches `eslint-disable-next-line <rule>` for any rule in `rule_names`.
39 fn matches_eslint_disable_next_line(value: &str, rule_names: &[String]) -> bool {
40 if let Some(rest) = value.strip_prefix("eslint-disable-next-line ") {
41 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
41 + return rule_names
42 + .iter()
43 + .any(|name| rest.starts_with(name.as_str()));
44 }
45 // Also check with leading space (comment values often have leading whitespace)
46 let trimmed = value.trim_start();
47 if let Some(rest) = trimmed.strip_prefix("eslint-disable-next-line ") {
46 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
48 + return rule_names
49 + .iter()
50 + .any(|name| rest.starts_with(name.as_str()));
51 }
52 false
53 }
@@ -51,11 +55,15 @@ fn matches_eslint_disable_next_line(value: &str, rule_names: &[String]) -> bool
55 /// Check if a comment value matches `eslint-disable <rule>` for any rule in `rule_names`.
56 fn matches_eslint_disable(value: &str, rule_names: &[String]) -> bool {
57 if let Some(rest) = value.strip_prefix("eslint-disable ") {
54 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
58 + return rule_names
59 + .iter()
60 + .any(|name| rest.starts_with(name.as_str()));
61 }
62 let trimmed = value.trim_start();
63 if let Some(rest) = trimmed.strip_prefix("eslint-disable ") {
58 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
64 + return rule_names
65 + .iter()
66 + .any(|name| rest.starts_with(name.as_str()));
67 }
68 false
69 }
@@ -63,11 +71,15 @@ fn matches_eslint_disable(value: &str, rule_names: &[String]) -> bool {
71 /// Check if a comment value matches `eslint-enable <rule>` for any rule in `rule_names`.
72 fn matches_eslint_enable(value: &str, rule_names: &[String]) -> bool {
73 if let Some(rest) = value.strip_prefix("eslint-enable ") {
66 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
74 + return rule_names
75 + .iter()
76 + .any(|name| rest.starts_with(name.as_str()));
77 }
78 let trimmed = value.trim_start();
79 if let Some(rest) = trimmed.strip_prefix("eslint-enable ") {
70 - return rule_names.iter().any(|name| rest.starts_with(name.as_str()));
80 + return rule_names
81 + .iter()
82 + .any(|name| rest.starts_with(name.as_str()));
83 }
84 false
85 }
@@ -135,10 +147,7 @@ pub fn find_program_suppressions(
147 }
148
149 // Check for Flow suppression (only if not already within a block)
138 - if flow_suppressions
139 - && disable_comment.is_none()
140 - && matches_flow_suppression(&data.value)
141 - {
150 + if flow_suppressions && disable_comment.is_none() && matches_flow_suppression(&data.value) {
151 disable_comment = Some(data.clone());
152 enable_comment = Some(data.clone());
153 source = Some(SuppressionSource::Flow);
compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs
+13 -25
@@ -19,10 +19,10 @@ use react_compiler_ast::expressions::{
19 ObjectExpressionProperty,
20 };
21 use react_compiler_ast::patterns::PatternLike;
22 -use react_compiler_ast::statements::{ForInit, ForInOfLeft, Statement, VariableDeclaration};
22 +use react_compiler_ast::statements::{ForInOfLeft, ForInit, Statement, VariableDeclaration};
23 use react_compiler_diagnostics::{
24 - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory,
25 - SourceLocation as DiagSourceLocation, Position as DiagPosition,
24 + CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, Position as DiagPosition,
25 + SourceLocation as DiagSourceLocation,
26 };
27 use react_compiler_hir::environment::Environment;
28 use react_compiler_lowering::FunctionNode;
@@ -54,7 +54,10 @@ pub fn validate_source_locations(
54 // (JS Map preserves insertion order, which is AST traversal order = source order)
55 let mut sorted_entries: Vec<&ImportantLocation> = important_original.values().collect();
56 sorted_entries.sort_by(|a, b| {
57 - a.loc.start.line.cmp(&b.loc.start.line)
57 + a.loc
58 + .start
59 + .line
60 + .cmp(&b.loc.start.line)
61 .then(a.loc.start.column.cmp(&b.loc.start.column))
62 // Outer nodes (larger spans) before inner nodes, matching depth-first traversal
63 .then(b.loc.end.line.cmp(&a.loc.end.line))
@@ -85,12 +88,7 @@ pub fn validate_source_locations(
88 if has_valid_node_type {
89 report_missing_location(env, &entry.loc, node_type);
90 } else {
88 - report_wrong_node_type(
89 - env,
90 - &entry.loc,
91 - node_type,
92 - generated_node_types,
93 - );
91 + report_wrong_node_type(env, &entry.loc, node_type, generated_node_types);
92 }
93 }
94 }
@@ -228,15 +226,12 @@ fn important_expression_type(expr: &Expression) -> Option<&'static str> {
226 fn is_manual_memoization(expr: &Expression) -> bool {
227 if let Expression::CallExpression(call) = expr {
228 match call.callee.as_ref() {
231 - Expression::Identifier(id) => {
232 - id.name == "useMemo" || id.name == "useCallback"
233 - }
229 + Expression::Identifier(id) => id.name == "useMemo" || id.name == "useCallback",
230 Expression::MemberExpression(mem) => {
231 if let (Expression::Identifier(obj), Expression::Identifier(prop)) =
232 (mem.object.as_ref(), &*mem.property)
233 {
238 - obj.name == "React"
239 - && (prop.name == "useMemo" || prop.name == "useCallback")
234 + obj.name == "React" && (prop.name == "useMemo" || prop.name == "useCallback")
235 } else {
236 false
237 }
@@ -679,8 +674,7 @@ fn collect_original_arrow_children(
674 }
675 match arrow.body.as_ref() {
676 ArrowFunctionBody::BlockStatement(block) => {
682 - let is_single_return =
683 - block.body.len() == 1 && block.directives.is_empty();
677 + let is_single_return = block.body.len() == 1 && block.directives.is_empty();
678 collect_original_block(&block.body, is_single_return, locations);
679 }
680 ArrowFunctionBody::Expression(expr) => {
@@ -741,10 +735,7 @@ fn collect_original_pattern(
735 collect_original_pattern(&r.argument, locations);
736 }
737 PatternLike::MemberExpression(m) => {
744 - collect_original_expression(
745 - &Expression::MemberExpression(m.clone()),
746 - locations,
747 - );
738 + collect_original_expression(&Expression::MemberExpression(m.clone()), locations);
739 }
740 PatternLike::TSAsExpression(_)
741 | PatternLike::TSSatisfiesExpression(_)
@@ -882,10 +873,7 @@ fn record_generated(
873 }
874 }
875
885 -fn collect_generated_statement(
886 - stmt: &Statement,
887 - locations: &mut HashMap<String, HashSet<String>>,
888 -) {
876 +fn collect_generated_statement(stmt: &Statement, locations: &mut HashMap<String, HashSet<String>>) {
877 // Record this statement's location
878 let type_name = statement_type_name(stmt);
879 record_generated(type_name, statement_loc(stmt), locations);
compiler/crates/react_compiler_ast/src/common.rs
+1 -2
@@ -55,8 +55,7 @@ impl RawNode {
55 /// RawNode text is valid JSON by construction, so failure here means a
56 /// broken invariant, not bad input; fail loudly rather than degrade.
57 pub fn parse_value(&self) -> serde_json::Value {
58 - from_json_str_unbounded(self.0.get())
59 - .expect("RawNode holds valid JSON by construction")
58 + from_json_str_unbounded(self.0.get()).expect("RawNode holds valid JSON by construction")
59 }
60
61 /// The node's `"type"` field, without parsing the whole subtree into a Value.
compiler/crates/react_compiler_ast/src/jsx.rs
+5 -1
@@ -14,7 +14,11 @@ pub struct JSXElement {
14 #[serde(rename = "closingElement")]
15 pub closing_element: Option<JSXClosingElement>,
16 pub children: Vec<JSXChild>,
17 - #[serde(rename = "selfClosing", default, skip_serializing_if = "Option::is_none")]
17 + #[serde(
18 + rename = "selfClosing",
19 + default,
20 + skip_serializing_if = "Option::is_none"
21 + )]
22 pub self_closing: Option<bool>,
23 }
24
compiler/crates/react_compiler_ast/src/statements.rs
+7 -7
@@ -1,10 +1,13 @@
1 +use serde::Deserialize;
2 +use serde::Deserializer;
3 +use serde::Serialize;
4 +use serde::Serializer;
5 use serde::de::Error as _;
2 -use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7 use crate::common::BaseNode;
8 use crate::common::RawNode;
6 -
7 -use crate::expressions::{Expression, Identifier};
9 +use crate::expressions::Expression;
10 +use crate::expressions::Identifier;
11 use crate::patterns::PatternLike;
12
13 fn is_false(v: &bool) -> bool {
@@ -119,10 +122,7 @@ impl UnknownStatement {
122 /// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()`
123 /// and `node_type()` cannot drift from `raw`. Mutations that remove the
124 /// string `type` field are rejected and rolled back.
122 - pub fn with_raw_mut<R>(
123 - &mut self,
124 - f: impl FnOnce(&mut RawNode) -> R,
125 - ) -> Result<R, String> {
125 + pub fn with_raw_mut<R>(&mut self, f: impl FnOnce(&mut RawNode) -> R) -> Result<R, String> {
126 let saved = self.raw.clone();
127 let result = f(&mut self.raw);
128 if self.raw.type_name().is_none() {
compiler/crates/react_compiler_ast/src/visitor.rs
+3 -9
@@ -590,16 +590,10 @@ impl<'a> AstWalker<'a> {
590 }
591 }
592 PatternLike::TSAsExpression(node) => self.walk_expression(v, &node.expression),
593 - PatternLike::TSSatisfiesExpression(node) => {
594 - self.walk_expression(v, &node.expression)
595 - }
596 - PatternLike::TSNonNullExpression(node) => {
597 - self.walk_expression(v, &node.expression)
598 - }
593 + PatternLike::TSSatisfiesExpression(node) => self.walk_expression(v, &node.expression),
594 + PatternLike::TSNonNullExpression(node) => self.walk_expression(v, &node.expression),
595 PatternLike::TSTypeAssertion(node) => self.walk_expression(v, &node.expression),
600 - PatternLike::TypeCastExpression(node) => {
601 - self.walk_expression(v, &node.expression)
602 - }
596 + PatternLike::TypeCastExpression(node) => self.walk_expression(v, &node.expression),
597 }
598 }
599
compiler/crates/react_compiler_ast/tests/deep_nesting.rs
+1 -3
@@ -17,9 +17,7 @@ fn statement_nested_beyond_default_recursion_limit_deserializes() {
17 let depth = 400;
18 let mut expr = r#"{"type":"Identifier","name":"x"}"#.to_string();
19 for _ in 0..depth {
20 - expr = format!(
21 - r#"{{"type":"CallExpression","callee":{expr},"arguments":[]}}"#
22 - );
20 + expr = format!(r#"{{"type":"CallExpression","callee":{expr},"arguments":[]}}"#);
21 }
22 let json = format!(
23 r#"{{"type":"File","program":{{"type":"Program","sourceType":"module","body":[{{"type":"ExpressionStatement","expression":{expr}}}],"directives":[]}}}}"#
compiler/crates/react_compiler_diagnostics/src/code_frame.rs
+14 -20
@@ -4,8 +4,7 @@
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7 -
8 -use crate::{CompilerDiagnosticDetail, CompilerErrorOrDiagnostic, CompilerError};
7 +use crate::{CompilerDiagnosticDetail, CompilerError, CompilerErrorOrDiagnostic};
8
9 const CODEFRAME_LINES_ABOVE: u32 = 2;
10 const CODEFRAME_LINES_BELOW: u32 = 3;
@@ -36,7 +35,9 @@ fn split_lines(source: &str) -> Vec<&str> {
35 } else {
36 // Check for Unicode line separators U+2028 and U+2029
37 // These are encoded as E2 80 A8 and E2 80 A9 in UTF-8
39 - if ch == 0xE2 && i + 2 < len && bytes[i + 1] == 0x80
38 + if ch == 0xE2
39 + && i + 2 < len
40 + && bytes[i + 1] == 0x80
41 && (bytes[i + 2] == 0xA8 || bytes[i + 2] == 0xA9)
42 {
43 lines.push(&source[start..i]);
@@ -157,7 +158,8 @@ pub fn code_frame_columns(
158 let number_max_width = format!("{}", end).len();
159
160 // Build a lookup map for marker lines
160 - let mut marker_map: std::collections::HashMap<usize, MarkerEntry> = std::collections::HashMap::new();
161 + let mut marker_map: std::collections::HashMap<usize, MarkerEntry> =
162 + std::collections::HashMap::new();
163 let line_diff = end_line as usize - start_line as usize;
164 for (line_number, entry) in marker_lines_raw {
165 // Resolve placeholder lengths using actual source lines
@@ -243,10 +245,8 @@ pub fn code_frame_columns(
245 let number_of_markers = if *len == 0 { 1 } else { *len };
246 let carets = "^".repeat(number_of_markers);
247 let gutter_spaces = gutter.replace(|c: char| c.is_ascii_digit(), " ");
246 - let mut marker_str = format!(
247 - "\n {} {}{}",
248 - gutter_spaces, marker_spacing, carets
249 - );
248 + let mut marker_str =
249 + format!("\n {} {}{}", gutter_spaces, marker_spacing, carets);
250 if last_marker_line && !message.is_empty() {
251 marker_str.push(' ');
252 marker_str.push_str(message);
@@ -272,12 +272,7 @@ pub fn code_frame_columns(
272
273 // If message is set but no columns, prepend the message
274 if !message.is_empty() && !has_columns {
275 - frame = format!(
276 - "{}{}\n{}",
277 - " ".repeat(number_max_width + 1),
278 - message,
279 - frame
280 - );
275 + frame = format!("{}{}\n{}", " ".repeat(number_max_width + 1), message, frame);
276 }
277
278 frame
@@ -331,11 +326,7 @@ use crate::format_category_heading;
326 /// The source parameter is the full source code of the file being compiled.
327 /// The filename parameter is the source filename (e.g., "foo.ts") used in
328 /// location displays.
334 -pub fn format_compiler_error(
335 - err: &CompilerError,
336 - source: &str,
337 - filename: Option<&str>,
338 -) -> String {
329 +pub fn format_compiler_error(err: &CompilerError, source: &str, filename: Option<&str>) -> String {
330 let detail_messages: Vec<String> = err
331 .details
332 .iter()
@@ -346,7 +337,10 @@ pub fn format_compiler_error(
337 let plural = if count == 1 { "" } else { "s" };
338 let header = format!("Found {} error{}:\n\n", count, plural);
339
349 - let trimmed: Vec<String> = detail_messages.iter().map(|m| m.trim().to_string()).collect();
340 + let trimmed: Vec<String> = detail_messages
341 + .iter()
342 + .map(|m| m.trim().to_string())
343 + .collect();
344 format!("{}{}", header, trimmed.join("\n\n"))
345 }
346
compiler/crates/react_compiler_diagnostics/src/lib.rs
+12 -13
@@ -1,6 +1,6 @@
1 pub mod code_frame;
2
3 -use serde::{Serialize, Deserialize};
3 +use serde::{Deserialize, Serialize};
4
5 /// Error categories matching the TS ErrorCategory enum
6 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -180,12 +180,16 @@ impl CompilerDiagnostic {
180
181 /// Create a diagnostic from a CompilerErrorDetail.
182 pub fn from_detail(detail: CompilerErrorDetail) -> Self {
183 - Self::new(detail.category, detail.reason.clone(), detail.description.clone())
184 - .with_detail(CompilerDiagnosticDetail::Error {
185 - loc: detail.loc,
186 - message: Some(detail.reason),
187 - identifier_name: None,
188 - })
183 + Self::new(
184 + detail.category,
185 + detail.reason.clone(),
186 + detail.description.clone(),
187 + )
188 + .with_detail(CompilerDiagnosticDetail::Error {
189 + loc: detail.loc,
190 + message: Some(detail.reason),
191 + identifier_name: None,
192 + })
193 }
194
195 pub fn primary_location(&self) -> Option<&SourceLocation> {
@@ -194,7 +198,6 @@ impl CompilerDiagnostic {
198 _ => None,
199 })
200 }
197 -
201 }
202
203 /// Legacy-style error detail (matches CompilerErrorDetail in TS)
@@ -387,11 +390,7 @@ impl From<CompilerError> for CompilerDiagnostic {
390 CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerDiagnostic::from_detail(d),
391 }
392 } else {
390 - CompilerDiagnostic::new(
391 - ErrorCategory::Invariant,
392 - "Unknown compiler error",
393 - None,
394 - )
393 + CompilerDiagnostic::new(ErrorCategory::Invariant, "Unknown compiler error", None)
394 }
395 }
396 }
compiler/crates/react_compiler_e2e_cli/Cargo.toml deleted
-25
@@ -1,25 +0,0 @@
1 -[package]
2 -name = "react_compiler_e2e_cli"
3 -version = "0.1.0"
4 -edition = "2024"
5 -
6 -[[bin]]
7 -name = "react-compiler-e2e"
8 -path = "src/main.rs"
9 -
10 -[dependencies]
11 -react_compiler = { path = "../react_compiler" }
12 -react_compiler_ast = { path = "../react_compiler_ast" }
13 -react_compiler_swc = { path = "../react_compiler_swc" }
14 -react_compiler_oxc = { path = "../react_compiler_oxc" }
15 -clap = { version = "4", features = ["derive"] }
16 -serde_json = "1"
17 -swc_ecma_parser = "35"
18 -swc_ecma_ast = "21"
19 -swc_common = "19"
20 -oxc_parser = "0.121"
21 -oxc_allocator = "0.121"
22 -oxc_span = "0.121"
23 -oxc_semantic = "0.121"
24 -oxc_codegen = "0.121"
25 -oxc_diagnostics = "0.121"
compiler/crates/react_compiler_e2e_cli/src/main.rs deleted
-310
@@ -1,310 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -//! CLI for end-to-end testing of the React Compiler via SWC or OXC frontends.
7 -//!
8 -//! Reads source from stdin, compiles via the chosen frontend, writes compiled
9 -//! code to stdout. Errors go to stderr. Exit 0 = success, exit 1 = error.
10 -//!
11 -//! With `--json`, outputs a JSON envelope to stdout containing code/error and
12 -//! logger events. Always exits 0 in JSON mode (errors are in the envelope).
13 -//!
14 -//! Usage:
15 -//! react-compiler-e2e --frontend <swc|oxc> --filename <path> [--options <json>] [--json]
16 -
17 -use std::io::Read;
18 -use std::process;
19 -
20 -use clap::Parser;
21 -use react_compiler::entrypoint::compile_result::LoggerEvent;
22 -use react_compiler::entrypoint::plugin_options::PluginOptions;
23 -
24 -#[derive(Parser)]
25 -#[command(name = "react-compiler-e2e")]
26 -struct Cli {
27 - /// Frontend to use: "swc" or "oxc"
28 - #[arg(long)]
29 - frontend: String,
30 -
31 - /// Filename (used to determine source type from extension)
32 - #[arg(long)]
33 - filename: String,
34 -
35 - /// JSON-serialized PluginOptions
36 - #[arg(long)]
37 - options: Option<String>,
38 -
39 - /// Output JSON envelope with code/error and logger events
40 - #[arg(long)]
41 - json: bool,
42 -
43 - /// Dump ScopeInfo as JSON to stderr (for debugging scope analysis differences)
44 - #[arg(long)]
45 - dump_scope: bool,
46 -}
47 -
48 -/// Result of compiling via a frontend, carrying both code/error and logger events.
49 -struct CompileOutput {
50 - code: Option<String>,
51 - error: Option<String>,
52 - events: Vec<LoggerEvent>,
53 -}
54 -
55 -fn main() {
56 - let cli = Cli::parse();
57 -
58 - // Read source from stdin
59 - let mut source = String::new();
60 - std::io::stdin()
61 - .read_to_string(&mut source)
62 - .unwrap_or_else(|e| {
63 - eprintln!("Failed to read stdin: {e}");
64 - process::exit(1);
65 - });
66 -
67 - // Parse options — merge provided JSON over sensible defaults
68 - let default_json = r#"{"shouldCompile":true,"enableReanimated":false,"isDev":false}"#;
69 - let options: PluginOptions = if let Some(ref json) = cli.options {
70 - // Merge: start with defaults, override with provided values
71 - let mut base: serde_json::Value = serde_json::from_str(default_json).unwrap();
72 - let overrides: serde_json::Value = serde_json::from_str(json).unwrap_or_else(|e| {
73 - eprintln!("Failed to parse options JSON: {e}");
74 - process::exit(1);
75 - });
76 - if let (serde_json::Value::Object(b), serde_json::Value::Object(o)) = (&mut base, overrides)
77 - {
78 - for (k, v) in o {
79 - b.insert(k, v);
80 - }
81 - }
82 - serde_json::from_value(base).unwrap_or_else(|e| {
83 - eprintln!("Failed to deserialize merged options: {e}");
84 - process::exit(1);
85 - })
86 - } else {
87 - serde_json::from_str(default_json).unwrap()
88 - };
89 -
90 - let output = match cli.frontend.as_str() {
91 - "swc" => compile_swc(&source, &cli.filename, options),
92 - "oxc" => compile_oxc(&source, &cli.filename, options, cli.dump_scope),
93 - other => {
94 - eprintln!("Unknown frontend: {other}. Use 'swc' or 'oxc'.");
95 - process::exit(1);
96 - }
97 - };
98 -
99 - if cli.json {
100 - // JSON envelope mode: always output JSON to stdout, exit 0
101 - let envelope = serde_json::json!({
102 - "code": output.code,
103 - "error": output.error,
104 - "events": output.events,
105 - });
106 - println!("{}", serde_json::to_string(&envelope).unwrap());
107 - } else {
108 - // Legacy mode: code to stdout, errors to stderr
109 - match (output.code, output.error) {
110 - (Some(code), _) => {
111 - print!("{code}");
112 - }
113 - (None, Some(e)) => {
114 - eprintln!("{e}");
115 - process::exit(1);
116 - }
117 - (None, None) => {
118 - process::exit(1);
119 - }
120 - }
121 - }
122 -}
123 -
124 -fn determine_swc_syntax(_filename: &str) -> swc_ecma_parser::Syntax {
125 - // Use TypeScript parser for all files including Flow files.
126 - // SWC doesn't have a native Flow parser, but the TS parser can handle
127 - // most Flow-compatible syntax (type aliases, type annotations, etc.).
128 - // The Babel test harness uses ['typescript', 'jsx'] for non-Flow files
129 - // and ['flow', 'jsx'] for Flow files, but SWC's TS parser is close
130 - // enough for our e2e test purposes.
131 - swc_ecma_parser::Syntax::Typescript(swc_ecma_parser::TsSyntax {
132 - tsx: true,
133 - ..Default::default()
134 - })
135 -}
136 -
137 -fn compile_swc(source: &str, filename: &str, mut options: PluginOptions) -> CompileOutput {
138 - options.filename = Some(filename.to_string());
139 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
140 - let fm = cm.new_source_file(
141 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
142 - source.to_string(),
143 - );
144 -
145 - let syntax = determine_swc_syntax(filename);
146 - let comments = swc_common::comments::SingleThreadedComments::default();
147 - let mut errors = vec![];
148 - let module = swc_ecma_parser::parse_file_as_module(
149 - &fm,
150 - syntax,
151 - swc_ecma_ast::EsVersion::latest(),
152 - Some(&comments),
153 - &mut errors,
154 - );
155 -
156 - let module = match module {
157 - Ok(m) => m,
158 - Err(e) => {
159 - return CompileOutput {
160 - code: None,
161 - error: Some(format!("SWC parse error: {e:?}")),
162 - events: vec![],
163 - };
164 - }
165 - };
166 -
167 - if !errors.is_empty() {
168 - return CompileOutput {
169 - code: None,
170 - error: Some(format!("SWC parse errors: {errors:?}")),
171 - events: vec![],
172 - };
173 - }
174 -
175 - let result = react_compiler_swc::transform(&module, source, options);
176 - let events = result.events;
177 -
178 - // Check for error-level diagnostics. When panicThreshold is "all_errors",
179 - // the TS/Babel plugin throws on any compilation error. We replicate this
180 - // behavior by returning an error when there are error diagnostics and
181 - // no compiled output.
182 - let has_errors = result
183 - .diagnostics
184 - .iter()
185 - .any(|d| matches!(d.severity, react_compiler_swc::diagnostics::Severity::Error));
186 -
187 - match result.module {
188 - Some(compiled_module) => CompileOutput {
189 - code: Some(react_compiler_swc::emit(&compiled_module)),
190 - error: None,
191 - events,
192 - },
193 - None => {
194 - if has_errors {
195 - // Compilation had errors — mimic TS plugin throwing
196 - let messages: Vec<String> = result
197 - .diagnostics
198 - .iter()
199 - .map(|d| d.message.clone())
200 - .collect();
201 - CompileOutput {
202 - code: None,
203 - error: Some(messages.join("\n")),
204 - events,
205 - }
206 - } else {
207 - // No changes needed — return the original source text
208 - // with directive blank lines normalized to match Babel's
209 - // codegen behavior. Babel always adds a blank line after
210 - // the last directive in a function/program body.
211 - CompileOutput {
212 - code: Some(react_compiler_swc::normalize_source(source)),
213 - error: None,
214 - events,
215 - }
216 - }
217 - }
218 - }
219 -}
220 -
221 -fn compile_oxc(
222 - source: &str,
223 - filename: &str,
224 - mut options: PluginOptions,
225 - dump_scope: bool,
226 -) -> CompileOutput {
227 - options.filename = Some(filename.to_string());
228 - // Always enable TypeScript parsing (like the TS/Babel baseline uses
229 - // ['typescript', 'jsx'] plugins). Some .js fixtures contain TS syntax.
230 - // Check for @script pragma in the first line to use script source type.
231 - let first_line = source.lines().next().unwrap_or("");
232 - let is_script = first_line.contains("@script");
233 - let source_type = oxc_span::SourceType::from_path(filename)
234 - .unwrap_or_default()
235 - .with_module(!is_script)
236 - .with_script(is_script)
237 - .with_jsx(true)
238 - .with_typescript(true);
239 -
240 - let allocator = oxc_allocator::Allocator::default();
241 - let parsed = oxc_parser::Parser::new(&allocator, source, source_type).parse();
242 -
243 - if parsed.panicked || !parsed.errors.is_empty() {
244 - let err_msgs: Vec<String> = parsed.errors.iter().map(|e| e.to_string()).collect();
245 - return CompileOutput {
246 - code: None,
247 - error: Some(format!("OXC parse errors: {}", err_msgs.join("; "))),
248 - events: vec![],
249 - };
250 - }
251 -
252 - let semantic = oxc_semantic::SemanticBuilder::new()
253 - .build(&parsed.program)
254 - .semantic;
255 -
256 - if dump_scope {
257 - let scope_info =
258 - react_compiler_oxc::convert_scope::convert_scope_info(&semantic, &parsed.program);
259 - eprintln!("{}", serde_json::to_string_pretty(&scope_info).unwrap());
260 - }
261 -
262 - let result = react_compiler_oxc::transform(&parsed.program, &semantic, source, options);
263 - let events = result.events;
264 - let rename_plan = result.rename_plan;
265 -
266 - // Check for error-level diagnostics, similar to SWC path.
267 - // OxcDiagnostic uses miette's Severity.
268 - let has_errors = result
269 - .diagnostics
270 - .iter()
271 - .any(|d| d.severity == oxc_diagnostics::Severity::Error);
272 -
273 - match result.file {
274 - Some(ref file) => {
275 - let emit_allocator = oxc_allocator::Allocator::default();
276 - CompileOutput {
277 - code: Some(react_compiler_oxc::emit(
278 - file,
279 - &emit_allocator,
280 - Some(source),
281 - &rename_plan,
282 - )),
283 - error: None,
284 - events,
285 - }
286 - }
287 - None => {
288 - if has_errors {
289 - // Compilation had errors — mimic TS plugin throwing
290 - let messages: Vec<String> = result
291 - .diagnostics
292 - .iter()
293 - .map(|d| d.message.to_string())
294 - .collect();
295 - CompileOutput {
296 - code: None,
297 - error: Some(messages.join("\n")),
298 - events,
299 - }
300 - } else {
301 - // No changes — emit the original parsed program (already has comments)
302 - CompileOutput {
303 - code: Some(oxc_codegen::Codegen::new().build(&parsed.program).code),
304 - error: None,
305 - events,
306 - }
307 - }
308 - }
309 - }
310 -}
compiler/crates/react_compiler_hir/src/default_module_type_provider.rs
+3 -3
@@ -9,11 +9,11 @@
9
10 use indexmap::IndexMap;
11
12 +use crate::Effect;
13 use crate::type_config::{
13 - FunctionTypeConfig, HookTypeConfig, ObjectTypeConfig, TypeConfig, TypeReferenceConfig,
14 - BuiltInTypeRef, ValueKind,
14 + BuiltInTypeRef, FunctionTypeConfig, HookTypeConfig, ObjectTypeConfig, TypeConfig,
15 + TypeReferenceConfig, ValueKind,
16 };
16 -use crate::Effect;
17
18 /// Returns type configuration for known third-party modules that are
19 /// incompatible with memoization. Ported from TS `defaultModuleTypeProvider`.
compiler/crates/react_compiler_hir/src/dominator.rs
+26 -20
@@ -31,7 +31,10 @@ impl PostDominator {
31 /// Returns the immediate post-dominator of the given block, or None if
32 /// the block post-dominates itself (i.e., it is the exit node).
33 pub fn get(&self, id: BlockId) -> Option<BlockId> {
34 - let dominator = self.nodes.get(&id).expect("Unknown node in post-dominator tree");
34 + let dominator = self
35 + .nodes
36 + .get(&id)
37 + .expect("Unknown node in post-dominator tree");
38 if *dominator == id {
39 None
40 } else {
@@ -112,12 +115,15 @@ fn build_reverse_graph(
115 let mut raw_nodes: HashMap<BlockId, Node> = HashMap::new();
116
117 // Create exit node
115 - raw_nodes.insert(exit_id, Node {
116 - id: exit_id,
117 - index: 0,
118 - preds: HashSet::new(),
119 - succs: HashSet::new(),
120 - });
118 + raw_nodes.insert(
119 + exit_id,
120 + Node {
121 + id: exit_id,
122 + index: 0,
123 + preds: HashSet::new(),
124 + succs: HashSet::new(),
125 + },
126 + );
127
128 for (id, block) in &func.body.blocks {
129 let successors = each_terminal_successor(&block.terminal);
@@ -132,12 +138,15 @@ fn build_reverse_graph(
138 raw_nodes.get_mut(&exit_id).unwrap().succs.insert(*id);
139 }
140
135 - raw_nodes.insert(*id, Node {
136 - id: *id,
137 - index: 0,
138 - preds: preds_set,
139 - succs: succs_set,
140 - });
141 + raw_nodes.insert(
142 + *id,
143 + Node {
144 + id: *id,
145 + index: 0,
146 + preds: preds_set,
147 + succs: succs_set,
148 + },
149 + );
150 }
151
152 // DFS from exit to compute RPO
@@ -185,7 +194,9 @@ fn dfs_postorder(
194 // Dominator fixpoint (Cooper/Harvey/Kennedy)
195 // =============================================================================
196
188 -fn compute_immediate_dominators(graph: &Graph) -> Result<HashMap<BlockId, BlockId>, CompilerDiagnostic> {
197 +fn compute_immediate_dominators(
198 + graph: &Graph,
199 +) -> Result<HashMap<BlockId, BlockId>, CompilerDiagnostic> {
200 let mut doms: HashMap<BlockId, BlockId> = HashMap::new();
201 doms.insert(graph.entry, graph.entry);
202
@@ -238,12 +249,7 @@ fn compute_immediate_dominators(graph: &Graph) -> Result<HashMap<BlockId, BlockI
249 Ok(doms)
250 }
251
241 -fn intersect(
242 - a: BlockId,
243 - b: BlockId,
244 - graph: &Graph,
245 - doms: &HashMap<BlockId, BlockId>,
246 -) -> BlockId {
252 +fn intersect(a: BlockId, b: BlockId, graph: &Graph, doms: &HashMap<BlockId, BlockId>) -> BlockId {
253 let mut block1 = graph.get_node(a);
254 let mut block2 = graph.get_node(b);
255 while block1.id != block2.id {
compiler/crates/react_compiler_hir/src/environment.rs
+5 -1
@@ -265,7 +265,11 @@ impl Environment {
265 /// Create a new MutableRange with a unique ID.
266 /// Use this when creating a logically new range (not copying an existing one).
267 /// To copy a range preserving its identity, use `.clone()` instead.
268 - pub fn new_mutable_range(&mut self, start: EvaluationOrder, end: EvaluationOrder) -> MutableRange {
268 + pub fn new_mutable_range(
269 + &mut self,
270 + start: EvaluationOrder,
271 + end: EvaluationOrder,
272 + ) -> MutableRange {
273 let id = MutableRangeId(self.next_mutable_range_id_counter);
274 self.next_mutable_range_id_counter += 1;
275 MutableRange { id, start, end }
compiler/crates/react_compiler_hir/src/environment_config.rs
+1 -2
@@ -11,8 +11,8 @@ use std::collections::HashMap;
11
12 use serde::{Deserialize, Serialize};
13
14 -use crate::type_config::{TypeConfig, ValueKind};
14 use crate::Effect;
15 +use crate::type_config::{TypeConfig, ValueKind};
16
17 /// External function reference (source module + import name).
18 /// Corresponds to TS `ExternalFunction`.
@@ -107,7 +107,6 @@ pub struct EnvironmentConfig {
107 pub validate_exhaustive_effect_dependencies: ExhaustiveEffectDepsMode,
108
109 // TODO: flowTypeProvider — requires JS function callback.
110 -
110 #[serde(default = "default_true")]
111 pub enable_optional_dependencies: bool,
112 #[serde(default)]
compiler/crates/react_compiler_hir/src/globals.rs
+9 -9
@@ -1350,7 +1350,7 @@ fn build_object_shape(shapes: &mut ShapeRegistry) {
1350 mixed_props.insert(
1351 "*".to_string(),
1352 Type::Object {
1353 - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()),
1353 + shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()),
1354 },
1355 );
1356 shapes.insert(
@@ -1370,7 +1370,7 @@ fn build_ref_shapes(shapes: &mut ShapeRegistry) {
1370 vec![(
1371 "current".to_string(),
1372 Type::Object {
1373 - shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()),
1373 + shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()),
1374 },
1375 )],
1376 );
@@ -1381,7 +1381,7 @@ fn build_ref_shapes(shapes: &mut ShapeRegistry) {
1381 vec![(
1382 "*".to_string(),
1383 Type::Object {
1384 - shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()),
1384 + shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()),
1385 },
1386 )],
1387 );
@@ -2141,9 +2141,9 @@ fn build_typed_globals(
2141
2142 // Math
2143 let math_fns: Vec<(String, Type)> = ["max", "min", "trunc", "ceil", "floor", "pow"]
2144 - .iter()
2145 - .map(|name| (name.to_string(), pure_primitive_fn(shapes)))
2146 - .collect();
2144 + .iter()
2145 + .map(|name| (name.to_string(), pure_primitive_fn(shapes)))
2146 + .collect();
2147 let mut math_props = math_fns;
2148 math_props.push(("PI".to_string(), Type::Primitive));
2149 // Math.random is impure
@@ -2209,9 +2209,9 @@ fn build_typed_globals(
2209
2210 // console
2211 let console_methods: Vec<(String, Type)> = ["error", "info", "log", "table", "trace", "warn"]
2212 - .iter()
2213 - .map(|name| (name.to_string(), pure_primitive_fn(shapes)))
2214 - .collect();
2212 + .iter()
2213 + .map(|name| (name.to_string(), pure_primitive_fn(shapes)))
2214 + .collect();
2215 let console_global = add_object(shapes, Some("console"), console_methods);
2216 typed_globals.push(("console".to_string(), console_global.clone()));
2217 globals.insert("console".to_string(), console_global);
compiler/crates/react_compiler_hir/src/lib.rs
+1 -9
@@ -1036,15 +1036,7 @@ impl IdentifierName {
1036 }
1037 }
1038
1039 -#[derive(
1040 - Debug,
1041 - Clone,
1042 - Copy,
1043 - PartialEq,
1044 - Eq,
1045 - serde::Serialize,
1046 - serde::Deserialize
1047 -)]
1039 +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1040 pub enum Effect {
1041 #[serde(rename = "<unknown>")]
1042 Unknown,
compiler/crates/react_compiler_hir/src/object_shape.rs
+2 -6
@@ -10,9 +10,9 @@
10
11 use std::collections::HashMap;
12
13 -use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason};
13 use crate::Effect;
14 use crate::Type;
15 +use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason};
16
17 // =============================================================================
18 // Shape ID constants (matching TS ObjectShape.ts)
@@ -240,11 +240,7 @@ pub fn add_function(
240
241 /// Add a hook to a ShapeRegistry.
242 /// Returns a `Type::Function` representing the added hook.
243 -pub fn add_hook(
244 - registry: &mut ShapeRegistry,
245 - sig: HookSignatureBuilder,
246 - id: Option<&str>,
247 -) -> Type {
243 +pub fn add_hook(registry: &mut ShapeRegistry, sig: HookSignatureBuilder, id: Option<&str>) -> Type {
244 let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id);
245 let return_type = sig.return_type.clone();
246 add_shape(
compiler/crates/react_compiler_hir/src/print.rs
+17 -17
@@ -51,28 +51,28 @@ pub fn format_loc_value(loc: &SourceLocation) -> String {
51 /// Format a string like JS `JSON.stringify`: escape control chars and quotes
52 /// but preserve non-ASCII unicode (e.g. U+00A0 nbsp) as literal characters.
53 pub fn format_js_string(s: &str) -> String {
54 - let mut result = String::with_capacity(s.len() + 2);
55 - result.push('"');
56 - for c in s.chars() {
57 - match c {
58 - '"' => result.push_str("\\\""),
59 - '\\' => result.push_str("\\\\"),
60 - '\n' => result.push_str("\\n"),
61 - '\r' => result.push_str("\\r"),
62 - '\t' => result.push_str("\\t"),
54 + let mut result = String::with_capacity(s.len() + 2);
55 + result.push('"');
56 + for c in s.chars() {
57 + match c {
58 + '"' => result.push_str("\\\""),
59 + '\\' => result.push_str("\\\\"),
60 + '\n' => result.push_str("\\n"),
61 + '\r' => result.push_str("\\r"),
62 + '\t' => result.push_str("\\t"),
63 '\u{0008}' => result.push_str("\\b"),
64 '\u{000c}' => result.push_str("\\f"),
65 - // Only escape C0 control chars (U+0000–U+001F), matching JS JSON.stringify.
66 - // Do NOT escape C1 controls (U+0080–U+009F) — JS outputs those as literal chars.
67 - c if (c as u32) <= 0x1F => {
65 + // Only escape C0 control chars (U+0000–U+001F), matching JS JSON.stringify.
66 + // Do NOT escape C1 controls (U+0080–U+009F) — JS outputs those as literal chars.
67 + c if (c as u32) <= 0x1F => {
68 result.push_str(&format!("\\u{:04x}", c as u32));
69 - }
70 - c => result.push(c),
71 - }
69 }
73 - result.push('"');
74 - result
70 + c => result.push(c),
71 }
72 + }
73 + result.push('"');
74 + result
75 +}
76
77 pub fn format_primitive(prim: &crate::PrimitiveValue) -> String {
78 match prim {
compiler/crates/react_compiler_hir/src/visitors.rs
+25 -59
@@ -4,15 +4,13 @@
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7 -
7 use std::collections::HashMap;
8
9 use crate::environment::Environment;
10 use crate::{
11 ArrayElement, ArrayPatternElement, BasicBlock, BlockId, HirFunction, IdentifierId, Instruction,
13 - InstructionKind, InstructionValue, JsxAttribute, JsxTag,
14 - ManualMemoDependencyRoot, ObjectPropertyKey, ObjectPropertyOrSpread, Pattern, Place,
15 - PlaceOrSpread, ScopeId, Terminal,
12 + InstructionKind, InstructionValue, JsxAttribute, JsxTag, ManualMemoDependencyRoot,
13 + ObjectPropertyKey, ObjectPropertyOrSpread, Pattern, Place, PlaceOrSpread, ScopeId, Terminal,
14 };
15
16 // =============================================================================
@@ -168,10 +166,7 @@ pub fn each_instruction_operand_with_functions(
166
167 /// Yields operand places from an InstructionValue.
168 /// Equivalent to TS `eachInstructionValueOperand`.
171 -pub fn each_instruction_value_operand(
172 - value: &InstructionValue,
173 - env: &Environment,
174 -) -> Vec<Place> {
169 +pub fn each_instruction_value_operand(value: &InstructionValue, env: &Environment) -> Vec<Place> {
170 each_instruction_value_operand_with_functions(value, &env.functions)
171 }
172
@@ -205,8 +200,7 @@ pub fn each_instruction_value_operand_with_functions(
200 InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {
201 // no operands
202 }
208 - InstructionValue::LoadLocal { place, .. }
209 - | InstructionValue::LoadContext { place, .. } => {
203 + InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
204 result.push(place.clone());
205 }
206 InstructionValue::StoreLocal { value: val, .. } => {
@@ -231,9 +225,7 @@ pub fn each_instruction_value_operand_with_functions(
225 result.push(object.clone());
226 }
227 InstructionValue::PropertyStore {
234 - object,
235 - value: val,
236 - ..
228 + object, value: val, ..
229 } => {
230 result.push(object.clone());
231 result.push(val.clone());
@@ -631,9 +623,7 @@ pub fn map_instruction_value_operands(
623 f: &mut impl FnMut(Place) -> Place,
624 ) {
625 match value {
634 - InstructionValue::BinaryExpression {
635 - left, right, ..
636 - } => {
626 + InstructionValue::BinaryExpression { left, right, .. } => {
627 *left = f(left.clone());
628 *right = f(right.clone());
629 }
@@ -644,9 +634,7 @@ pub fn map_instruction_value_operands(
634 *object = f(object.clone());
635 }
636 InstructionValue::PropertyStore {
647 - object,
648 - value: val,
649 - ..
637 + object, value: val, ..
638 } => {
639 *object = f(object.clone());
640 *val = f(val.clone());
@@ -676,8 +664,7 @@ pub fn map_instruction_value_operands(
664 InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {
665 // no operands
666 }
679 - InstructionValue::LoadLocal { place, .. }
680 - | InstructionValue::LoadContext { place, .. } => {
667 + InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
668 *place = f(place.clone());
669 }
670 InstructionValue::StoreLocal { value: val, .. } => {
@@ -906,9 +893,7 @@ pub fn map_terminal_successors(terminal: &mut Terminal, f: &mut impl FnMut(Block
893 *fallthrough = f(*fallthrough);
894 }
895 Terminal::Switch {
909 - cases,
910 - fallthrough,
911 - ..
896 + cases, fallthrough, ..
897 } => {
898 for case in cases.iter_mut() {
899 case.block = f(case.block);
@@ -994,17 +979,13 @@ pub fn map_terminal_successors(terminal: &mut Terminal, f: &mut impl FnMut(Block
979 *fallthrough = f(*fallthrough);
980 }
981 Terminal::Label {
997 - block,
998 - fallthrough,
999 - ..
982 + block, fallthrough, ..
983 } => {
984 *block = f(*block);
985 *fallthrough = f(*fallthrough);
986 }
987 Terminal::Sequence {
1005 - block,
1006 - fallthrough,
1007 - ..
988 + block, fallthrough, ..
989 } => {
990 *block = f(*block);
991 *fallthrough = f(*fallthrough);
@@ -1030,14 +1011,10 @@ pub fn map_terminal_successors(terminal: &mut Terminal, f: &mut impl FnMut(Block
1011 *fallthrough = f(*fallthrough);
1012 }
1013 Terminal::Scope {
1033 - block,
1034 - fallthrough,
1035 - ..
1014 + block, fallthrough, ..
1015 }
1016 | Terminal::PrunedScope {
1038 - block,
1039 - fallthrough,
1040 - ..
1017 + block, fallthrough, ..
1018 } => {
1019 *block = f(*block);
1020 *fallthrough = f(*fallthrough);
@@ -1125,9 +1102,7 @@ pub fn each_terminal_all_successors(terminal: &Terminal) -> Vec<BlockId> {
1102 result.push(*fallthrough);
1103 }
1104 Terminal::Switch {
1128 - cases,
1129 - fallthrough,
1130 - ..
1105 + cases, fallthrough, ..
1106 } => {
1107 for case in cases {
1108 result.push(case.block);
@@ -1206,14 +1181,10 @@ pub fn each_terminal_all_successors(terminal: &Terminal) -> Vec<BlockId> {
1181 result.push(*fallthrough);
1182 }
1183 Terminal::Label {
1209 - block,
1210 - fallthrough,
1211 - ..
1184 + block, fallthrough, ..
1185 }
1186 | Terminal::Sequence {
1214 - block,
1215 - fallthrough,
1216 - ..
1187 + block, fallthrough, ..
1188 } => {
1189 result.push(*block);
1190 result.push(*fallthrough);
@@ -1239,14 +1210,10 @@ pub fn each_terminal_all_successors(terminal: &Terminal) -> Vec<BlockId> {
1210 result.push(*fallthrough);
1211 }
1212 Terminal::Scope {
1242 - block,
1243 - fallthrough,
1244 - ..
1213 + block, fallthrough, ..
1214 }
1215 | Terminal::PrunedScope {
1247 - block,
1248 - fallthrough,
1249 - ..
1216 + block, fallthrough, ..
1217 } => {
1218 result.push(*block);
1219 result.push(*fallthrough);
@@ -1454,7 +1421,10 @@ pub fn each_instruction_operand_ids(instr: &Instruction, env: &Environment) -> V
1421
1422 /// Collect all operand IdentifierIds from an instruction value.
1423 /// Convenience wrapper around `each_instruction_value_operand` that maps to ids.
1457 -pub fn each_instruction_value_operand_ids(value: &InstructionValue, env: &Environment) -> Vec<IdentifierId> {
1424 +pub fn each_instruction_value_operand_ids(
1425 + value: &InstructionValue,
1426 + env: &Environment,
1427 +) -> Vec<IdentifierId> {
1428 each_instruction_value_operand(value, env)
1429 .into_iter()
1430 .map(|p| p.identifier)
@@ -1513,9 +1483,7 @@ pub fn for_each_instruction_value_operand_mut(
1483 f(object);
1484 }
1485 InstructionValue::PropertyStore {
1516 - object,
1517 - value: val,
1518 - ..
1486 + object, value: val, ..
1487 } => {
1488 f(object);
1489 f(val);
@@ -1540,8 +1508,7 @@ pub fn for_each_instruction_value_operand_mut(
1508 f(val);
1509 }
1510 InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {}
1543 - InstructionValue::LoadLocal { place, .. }
1544 - | InstructionValue::LoadContext { place, .. } => {
1511 + InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
1512 f(place);
1513 }
1514 InstructionValue::StoreLocal { value: val, .. } => {
@@ -1627,8 +1594,7 @@ pub fn for_each_instruction_value_operand_mut(
1594 f(child);
1595 }
1596 }
1630 - InstructionValue::FunctionExpression { .. }
1631 - | InstructionValue::ObjectMethod { .. } => {
1597 + InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. } => {
1598 // Context places require env access — callers handle separately.
1599 }
1600 InstructionValue::TaggedTemplateExpression { tag, .. } => {
compiler/crates/react_compiler_inference/src/align_method_call_scopes.rs
+6 -11
@@ -34,10 +34,8 @@ pub fn align_method_call_scopes(func: &mut HirFunction, env: &mut Environment) {
34 let instr = &func.instructions[instr_id.0 as usize];
35 match &instr.value {
36 InstructionValue::MethodCall { property, .. } => {
37 - let lvalue_scope =
38 - env.identifiers[instr.lvalue.identifier.0 as usize].scope;
39 - let property_scope =
40 - env.identifiers[property.identifier.0 as usize].scope;
37 + let lvalue_scope = env.identifiers[instr.lvalue.identifier.0 as usize].scope;
38 + let property_scope = env.identifiers[property.identifier.0 as usize].scope;
39
40 match (lvalue_scope, property_scope) {
41 (Some(lvalue_sid), Some(property_sid)) => {
@@ -47,8 +45,7 @@ pub fn align_method_call_scopes(func: &mut HirFunction, env: &mut Environment) {
45 (Some(lvalue_sid), None) => {
46 // Call has a scope but not the property:
47 // record that this property should be in this scope
50 - scope_mapping
51 - .insert(property.identifier, Some(lvalue_sid));
48 + scope_mapping.insert(property.identifier, Some(lvalue_sid));
49 }
50 (None, Some(_)) => {
51 // Property has a scope but call doesn't:
@@ -91,8 +88,8 @@ pub fn align_method_call_scopes(func: &mut HirFunction, env: &mut Environment) {
88 let entry = range_updates
89 .entry(root_id)
90 .or_insert_with(|| (root_range.start, root_range.end));
94 - entry.0 = EvaluationOrder(std::cmp::min(entry.0 .0, scope_range.start.0));
95 - entry.1 = EvaluationOrder(std::cmp::max(entry.1 .0, scope_range.end.0));
91 + entry.0 = EvaluationOrder(std::cmp::min(entry.0.0, scope_range.start.0));
92 + entry.1 = EvaluationOrder(std::cmp::max(entry.1.0, scope_range.end.0));
93 });
94
95 // Save original scope range IDs before updating
@@ -130,9 +127,7 @@ pub fn align_method_call_scopes(func: &mut HirFunction, env: &mut Environment) {
127
128 if let Some(mapped_scope) = scope_mapping.get(&lvalue_id) {
129 env.identifiers[lvalue_id.0 as usize].scope = *mapped_scope;
133 - } else if let Some(current_scope) =
134 - env.identifiers[lvalue_id.0 as usize].scope
135 - {
130 + } else if let Some(current_scope) = env.identifiers[lvalue_id.0 as usize].scope {
131 // TS: mergedScopes.find() returns null if not in the set
132 if let Some(merged) = merged_scopes.find_opt(current_scope) {
133 env.identifiers[lvalue_id.0 as usize].scope = Some(merged);
compiler/crates/react_compiler_inference/src/align_object_method_scopes.rs
+3 -3
@@ -110,9 +110,9 @@ pub fn align_object_method_scopes(func: &mut HirFunction, env: &mut Environment)
110 let scope_range = env.scopes[scope_id.0 as usize].range.clone();
111 let root_range = env.scopes[root_id.0 as usize].range.clone();
112
113 - let entry = range_updates.entry(root_id).or_insert_with(|| {
114 - (root_range.start, root_range.end)
115 - });
113 + let entry = range_updates
114 + .entry(root_id)
115 + .or_insert_with(|| (root_range.start, root_range.end));
116 entry.0 = EvaluationOrder(cmp::min(entry.0.0, scope_range.start.0));
117 entry.1 = EvaluationOrder(cmp::max(entry.1.0, scope_range.end.0));
118 });
compiler/crates/react_compiler_inference/src/analyse_functions.rs
+18 -15
@@ -18,8 +18,8 @@ use react_compiler_hir::environment::Environment;
18 use std::collections::HashSet;
19
20 use react_compiler_hir::{
21 - AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, IdentifierId,
22 - InstructionValue, Place, ReactFunctionType, HIR,
21 + AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId,
22 + InstructionValue, Place, ReactFunctionType,
23 };
24
25 /// Analyse all nested function expressions and object methods in `func`.
@@ -33,7 +33,11 @@ use react_compiler_hir::{
33 /// `lowerWithMutationAliasing`.
34 ///
35 /// Corresponds to TS `analyseFunctions(func: HIRFunction): void`.
36 -pub fn analyse_functions<F>(func: &mut HirFunction, env: &mut Environment, debug_logger: &mut F) -> Result<(), CompilerDiagnostic>
36 +pub fn analyse_functions<F>(
37 + func: &mut HirFunction,
38 + env: &mut Environment,
39 + debug_logger: &mut F,
40 +) -> Result<(), CompilerDiagnostic>
41 where
42 F: FnMut(&HirFunction, &Environment),
43 {
@@ -92,7 +96,11 @@ where
96 /// Run mutation/aliasing inference on an inner function.
97 ///
98 /// Corresponds to TS `lowerWithMutationAliasing(fn: HIRFunction): void`.
95 -fn lower_with_mutation_aliasing<F>(func: &mut HirFunction, env: &mut Environment, debug_logger: &mut F) -> Result<(), CompilerDiagnostic>
99 +fn lower_with_mutation_aliasing<F>(
100 + func: &mut HirFunction,
101 + env: &mut Environment,
102 + debug_logger: &mut F,
103 +) -> Result<(), CompilerDiagnostic>
104 where
105 F: FnMut(&HirFunction, &Environment),
106 {
@@ -100,9 +108,7 @@ where
108 analyse_functions(func, env, debug_logger)?;
109
110 // inferMutationAliasingEffects on the inner function
103 - crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(
104 - func, env, true,
105 - )?;
111 + crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(func, env, true)?;
112
113 // Check for invariant errors (e.g., uninitialized value kind)
114 // In TS, these throw from within inferMutationAliasingEffects, aborting
@@ -115,12 +121,12 @@ where
121 react_compiler_optimization::dead_code_elimination(func, env);
122
123 // inferMutationAliasingRanges — returns the externally-visible function effects
118 - let function_effects = crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(
119 - func, env, true,
120 - )?;
124 + let function_effects =
125 + crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(func, env, true)?;
126
127 // rewriteInstructionKindsBasedOnReassignment
123 - if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env) {
128 + if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env)
129 + {
130 env.errors.merge(err);
131 return Ok(());
132 }
@@ -169,9 +175,7 @@ where
175 }
176
177 for operand in &mut func.context {
172 - if captured_or_mutated.contains(&operand.identifier)
173 - || operand.effect == Effect::Capture
174 - {
178 + if captured_or_mutated.contains(&operand.identifier) || operand.effect == Effect::Capture {
179 operand.effect = Effect::Capture;
180 } else {
181 operand.effect = Effect::Read;
@@ -184,7 +188,6 @@ where
188 Ok(())
189 }
190
187 -
191 /// Create a placeholder HirFunction for temporarily swapping an inner function
192 /// out of `env.functions` via `std::mem::replace`. The placeholder is never
193 /// read — the real function is swapped back immediately after processing.
compiler/crates/react_compiler_inference/src/build_reactive_scope_terminals_hir.rs
+2 -5
@@ -374,11 +374,8 @@ fn fix_scope_and_identifier_ranges(func: &HirFunction, env: &mut Environment) {
374 // reference as scope.range see the update automatically. We simulate
375 // this by only syncing identifiers whose mutableRange matches the
376 // scope's pre-update range.
377 - let original_scope_ranges: Vec<react_compiler_hir::MutableRange> = env
378 - .scopes
379 - .iter()
380 - .map(|s| s.range.clone())
381 - .collect();
377 + let original_scope_ranges: Vec<react_compiler_hir::MutableRange> =
378 + env.scopes.iter().map(|s| s.range.clone()).collect();
379
380 for (_block_id, block) in &func.body.blocks {
381 match &block.terminal {
compiler/crates/react_compiler_inference/src/flatten_scopes_with_hooks_or_use_hir.rs
+7 -7
@@ -34,7 +34,10 @@ use react_compiler_hir::{BlockId, HirFunction, InstructionValue, Terminal, Type}
34 ///
35 /// Hooks and `use` must be called unconditionally, so any reactive scope containing
36 /// such a call must be flattened to avoid making the call conditional.
37 -pub fn flatten_scopes_with_hooks_or_use_hir(func: &mut HirFunction, env: &Environment) -> Result<(), CompilerDiagnostic> {
37 +pub fn flatten_scopes_with_hooks_or_use_hir(
38 + func: &mut HirFunction,
39 + env: &Environment,
40 +) -> Result<(), CompilerDiagnostic> {
41 let mut active_scopes: Vec<ActiveScope> = Vec::new();
42 let mut prune: Vec<BlockId> = Vec::new();
43
@@ -52,8 +55,8 @@ pub fn flatten_scopes_with_hooks_or_use_hir(func: &mut HirFunction, env: &Enviro
55 let instr = &func.instructions[instr_id.0 as usize];
56 match &instr.value {
57 InstructionValue::CallExpression { callee, .. } => {
55 - let callee_ty = &env.types
56 - [env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
58 + let callee_ty =
59 + &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
60 if is_hook_or_use(env, callee_ty)? {
61 // All active scopes must be pruned
62 prune.extend(active_scopes.iter().map(|s| s.block));
@@ -73,10 +76,7 @@ pub fn flatten_scopes_with_hooks_or_use_hir(func: &mut HirFunction, env: &Enviro
76 }
77
78 // Track scope terminals
76 - if let Terminal::Scope {
77 - fallthrough, ..
78 - } = &block.terminal
79 - {
79 + if let Terminal::Scope { fallthrough, .. } = &block.terminal {
80 active_scopes.push(ActiveScope {
81 block: *block_id,
82 fallthrough: *fallthrough,
compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_ranges.rs
+54 -41
@@ -151,7 +151,12 @@ impl AliasingState {
151 node: into_id,
152 kind: EdgeKind::Capture,
153 });
154 - self.nodes.get_mut(&into_id).unwrap().captures.entry(from_id).or_insert(index);
154 + self.nodes
155 + .get_mut(&into_id)
156 + .unwrap()
157 + .captures
158 + .entry(from_id)
159 + .or_insert(index);
160 }
161
162 fn assign(&mut self, index: usize, from: &Place, into: &Place) {
@@ -165,7 +170,12 @@ impl AliasingState {
170 node: into_id,
171 kind: EdgeKind::Alias,
172 });
168 - self.nodes.get_mut(&into_id).unwrap().aliases.entry(from_id).or_insert(index);
173 + self.nodes
174 + .get_mut(&into_id)
175 + .unwrap()
176 + .aliases
177 + .entry(from_id)
178 + .or_insert(index);
179 }
180
181 fn maybe_alias(&mut self, index: usize, from: &Place, into: &Place) {
@@ -179,7 +189,12 @@ impl AliasingState {
189 node: into_id,
190 kind: EdgeKind::MaybeAlias,
191 });
182 - self.nodes.get_mut(&into_id).unwrap().maybe_aliases.entry(from_id).or_insert(index);
192 + self.nodes
193 + .get_mut(&into_id)
194 + .unwrap()
195 + .maybe_aliases
196 + .entry(from_id)
197 + .or_insert(index);
198 }
199
200 fn render(&self, index: usize, start: IdentifierId, env: &mut Environment) {
@@ -275,9 +290,7 @@ impl AliasingState {
290
291 if let Some(end_val) = end {
292 let ident = &mut env.identifiers[node.id.0 as usize];
278 - ident.mutable_range.end = EvaluationOrder(
279 - ident.mutable_range.end.0.max(end_val.0),
280 - );
293 + ident.mutable_range.end = EvaluationOrder(ident.mutable_range.end.0.max(end_val.0));
294 }
295
296 if let NodeValue::Function { function_id } = &node.value {
@@ -576,10 +589,8 @@ pub fn infer_mutation_aliasing_ranges(
589 }
590 AliasingEffect::MutateTransitive { value }
591 | AliasingEffect::MutateTransitiveConditionally { value } => {
579 - let is_transitive_conditional = matches!(
580 - effect,
581 - AliasingEffect::MutateTransitiveConditionally { .. }
582 - );
592 + let is_transitive_conditional =
593 + matches!(effect, AliasingEffect::MutateTransitiveConditionally { .. });
594 mutations.push(PendingMutation {
595 index: index,
596 id: instr_eval_order,
@@ -785,7 +796,10 @@ pub fn infer_mutation_aliasing_ranges(
796
797 (
798 phi.place.identifier,
788 - phi.operands.values().map(|o| o.identifier).collect::<Vec<_>>(),
799 + phi.operands
800 + .values()
801 + .map(|o| o.identifier)
802 + .collect::<Vec<_>>(),
803 is_mutated_after_creation,
804 first_instr_id,
805 )
@@ -813,8 +827,7 @@ pub fn infer_mutation_aliasing_ranges(
827 if *is_mutated_after_creation {
828 let ident = &mut env.identifiers[phi_id.0 as usize];
829 if ident.mutable_range.start == EvaluationOrder(0) {
816 - ident.mutable_range.start =
817 - EvaluationOrder(first_instr_id.0.saturating_sub(1));
830 + ident.mutable_range.start = EvaluationOrder(first_instr_id.0.saturating_sub(1));
831 }
832 }
833 }
@@ -835,37 +848,42 @@ pub fn infer_mutation_aliasing_ranges(
848 ident.mutable_range.start = eval_order;
849 }
850 if ident.mutable_range.end == EvaluationOrder(0) {
838 - ident.mutable_range.end = EvaluationOrder(
839 - (eval_order.0 + 1).max(ident.mutable_range.end.0),
840 - );
851 + ident.mutable_range.end =
852 + EvaluationOrder((eval_order.0 + 1).max(ident.mutable_range.end.0));
853 }
854 }
855 func.instructions[instr_id.0 as usize].lvalue.effect = Effect::ConditionallyMutate;
856
857 // Also handle value-level lvalues (DeclareLocal, StoreLocal, etc.)
846 - let value_lvalue_ids: Vec<IdentifierId> = each_instruction_value_lvalue(&func.instructions[instr_id.0 as usize].value)
847 - .into_iter()
848 - .map(|p| p.identifier)
849 - .collect();
858 + let value_lvalue_ids: Vec<IdentifierId> =
859 + each_instruction_value_lvalue(&func.instructions[instr_id.0 as usize].value)
860 + .into_iter()
861 + .map(|p| p.identifier)
862 + .collect();
863 for vlid in &value_lvalue_ids {
864 let ident = &mut env.identifiers[vlid.0 as usize];
865 if ident.mutable_range.start == EvaluationOrder(0) {
866 ident.mutable_range.start = eval_order;
867 }
868 if ident.mutable_range.end == EvaluationOrder(0) {
856 - ident.mutable_range.end = EvaluationOrder(
857 - (eval_order.0 + 1).max(ident.mutable_range.end.0),
858 - );
869 + ident.mutable_range.end =
870 + EvaluationOrder((eval_order.0 + 1).max(ident.mutable_range.end.0));
871 }
872 }
861 - for_each_instruction_value_lvalue_mut(&mut func.instructions[instr_id.0 as usize].value, &mut |place| {
862 - place.effect = Effect::ConditionallyMutate;
863 - });
873 + for_each_instruction_value_lvalue_mut(
874 + &mut func.instructions[instr_id.0 as usize].value,
875 + &mut |place| {
876 + place.effect = Effect::ConditionallyMutate;
877 + },
878 + );
879
880 // Set operand effects to Read
866 - for_each_instruction_value_operand_mut(&mut func.instructions[instr_id.0 as usize].value, &mut |place| {
867 - place.effect = Effect::Read;
868 - });
881 + for_each_instruction_value_operand_mut(
882 + &mut func.instructions[instr_id.0 as usize].value,
883 + &mut |place| {
884 + place.effect = Effect::Read;
885 + },
886 + );
887
888 let instr = &func.instructions[instr_id.0 as usize];
889 if instr.effects.is_none() {
@@ -883,14 +901,12 @@ pub fn infer_mutation_aliasing_ranges(
901 | AliasingEffect::Capture { from, into }
902 | AliasingEffect::CreateFrom { from, into }
903 | AliasingEffect::MaybeAlias { from, into } => {
886 - let is_mutated_or_reassigned = env.identifiers
887 - [into.identifier.0 as usize]
904 + let is_mutated_or_reassigned = env.identifiers[into.identifier.0 as usize]
905 .mutable_range
906 .end
907 > eval_order;
908 if is_mutated_or_reassigned {
892 - operand_effects
893 - .insert(from.identifier, Effect::Capture);
909 + operand_effects.insert(from.identifier, Effect::Capture);
910 operand_effects.insert(into.identifier, Effect::Store);
911 } else {
912 operand_effects.insert(from.identifier, Effect::Read);
@@ -913,8 +929,7 @@ pub fn infer_mutation_aliasing_ranges(
929 AliasingEffect::MutateTransitive { value, .. }
930 | AliasingEffect::MutateConditionally { value }
931 | AliasingEffect::MutateTransitiveConditionally { value } => {
916 - operand_effects
917 - .insert(value.identifier, Effect::ConditionallyMutate);
932 + operand_effects.insert(value.identifier, Effect::ConditionallyMutate);
933 }
934 AliasingEffect::Freeze { value, .. } => {
935 operand_effects.insert(value.identifier, Effect::Freeze);
@@ -952,8 +967,9 @@ pub fn infer_mutation_aliasing_ranges(
967 if ident.mutable_range.end > eval_order
968 && ident.mutable_range.start == EvaluationOrder(0)
969 {
955 - env.identifiers[place.identifier.0 as usize].mutable_range.start =
956 - eval_order;
970 + env.identifiers[place.identifier.0 as usize]
971 + .mutable_range
972 + .start = eval_order;
973 }
974 // Apply effect
975 if let Some(&effect) = operand_effects.get(&place.identifier) {
@@ -1076,9 +1092,7 @@ pub fn infer_mutation_aliasing_ranges(
1092
1093 for j in 0..tracked.len() {
1094 let from = &tracked[j];
1079 - if from.identifier == into.identifier
1080 - || from.identifier == returns_identifier_id
1081 - {
1095 + if from.identifier == into.identifier || from.identifier == returns_identifier_id {
1096 continue;
1097 }
1098
@@ -1167,4 +1181,3 @@ fn collect_param_effects(
1181 }
1182 }
1183 }
1170 -
compiler/crates/react_compiler_inference/src/infer_reactive_places.rs
+25 -43
@@ -75,12 +75,8 @@ pub fn infer_reactive_places(
75 loop {
76 for block_id in &block_ids {
77 let block = func.body.blocks.get(block_id).unwrap();
78 - let has_reactive_control = is_reactive_controlled_block(
79 - block.id,
80 - func,
81 - &post_dominators,
82 - &mut reactive_map,
83 - );
78 + let has_reactive_control =
79 + is_reactive_controlled_block(block.id, func, &post_dominators, &mut reactive_map);
80
81 // Process phi nodes
82 let block = func.body.blocks.get(block_id).unwrap();
@@ -186,9 +182,8 @@ pub fn infer_reactive_places(
182 | Effect::ConditionallyMutate
183 | Effect::ConditionallyMutateIterator
184 | Effect::Mutate => {
189 - let op_range = &env.identifiers
190 - [op_place.identifier.0 as usize]
191 - .mutable_range;
185 + let op_range =
186 + &env.identifiers[op_place.identifier.0 as usize].mutable_range;
187 if op_range.contains(instr.id) {
188 reactive_map.mark_reactive(op_place.identifier);
189 }
@@ -199,10 +194,7 @@ pub fn infer_reactive_places(
194 Effect::Unknown => {
195 return Err(CompilerDiagnostic::new(
196 ErrorCategory::Invariant,
202 - &format!(
203 - "Unexpected unknown effect at {:?}",
204 - op_place.loc
205 - ),
197 + &format!("Unexpected unknown effect at {:?}", op_place.loc),
198 None,
199 ));
200 }
@@ -291,11 +283,7 @@ impl StableSidemap {
283 }
284 }
285
294 - fn handle_instruction(
295 - &mut self,
296 - instr: &react_compiler_hir::Instruction,
297 - env: &Environment,
298 - ) {
286 + fn handle_instruction(&mut self, instr: &react_compiler_hir::Instruction, env: &Environment) {
287 let lvalue_id = instr.lvalue.identifier;
288 let value = &instr.value;
289
@@ -314,8 +302,8 @@ impl StableSidemap {
302 }
303 }
304 InstructionValue::MethodCall { property, .. } => {
317 - let property_ty = &env.types
318 - [env.identifiers[property.identifier.0 as usize].type_.0 as usize];
305 + let property_ty =
306 + &env.types[env.identifiers[property.identifier.0 as usize].type_.0 as usize];
307 if evaluates_to_stable_type_or_container(env, property_ty) {
308 let lvalue_ty =
309 &env.types[env.identifiers[lvalue_id.0 as usize].type_.0 as usize];
@@ -346,8 +334,7 @@ impl StableSidemap {
334 .map(|p| p.identifier)
335 .collect();
336 for lid in lvalue_ids {
349 - let lid_ty =
350 - &env.types[env.identifiers[lid.0 as usize].type_.0 as usize];
337 + let lid_ty = &env.types[env.identifiers[lid.0 as usize].type_.0 as usize];
338 if is_stable_type_container(lid_ty) {
339 self.map.insert(lid, false);
340 } else if is_stable_type(lid_ty) {
@@ -442,9 +429,7 @@ fn is_stable_type(ty: &Type) -> bool {
429 | "BuiltInSetOptimistic"
430 )
431 }
445 - Type::Object {
446 - shape_id: Some(id),
447 - } => {
432 + Type::Object { shape_id: Some(id) } => {
433 matches!(id.as_str(), "BuiltInUseRefId")
434 }
435 _ => false,
@@ -453,9 +438,7 @@ fn is_stable_type(ty: &Type) -> bool {
438
439 fn is_stable_type_container(ty: &Type) -> bool {
440 match ty {
456 - Type::Object {
457 - shape_id: Some(id),
458 - } => {
441 + Type::Object { shape_id: Some(id) } => {
442 matches!(
443 id.as_str(),
444 "BuiltInUseState"
@@ -585,8 +568,7 @@ fn apply_reactive_flags_replay(
568 }
569
570 for (op_idx, (_pred, operand)) in phi.operands.iter_mut().enumerate() {
588 - if let Some(&is_reactive) =
589 - phi_operand_reactive.get(&(*block_id, phi_idx, op_idx))
571 + if let Some(&is_reactive) = phi_operand_reactive.get(&(*block_id, phi_idx, op_idx))
572 {
573 if is_reactive {
574 operand.reactive = true;
@@ -618,9 +600,12 @@ fn apply_reactive_flags_replay(
600 // Check hooks/use
601 match &instr.value {
602 InstructionValue::CallExpression { callee, .. } => {
621 - let callee_ty = &env.types
622 - [env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
623 - if get_hook_kind_for_type(env, callee_ty).ok().flatten().is_some()
603 + let callee_ty =
604 + &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
605 + if get_hook_kind_for_type(env, callee_ty)
606 + .ok()
607 + .flatten()
608 + .is_some()
609 || is_use_operator_type(callee_ty)
610 {
611 has_reactive_input = true;
@@ -679,16 +664,13 @@ fn apply_reactive_flags_replay(
664 }
665 }
666 InstructionValue::Destructure { lvalue, .. } => {
682 - visitors::for_each_pattern_operand_mut(
683 - &mut lvalue.pattern,
684 - &mut |place| {
685 - if !stable_sidemap.is_stable(place.identifier)
686 - && reactive_ids.contains(&place.identifier)
687 - {
688 - place.reactive = true;
689 - }
690 - },
691 - );
667 + visitors::for_each_pattern_operand_mut(&mut lvalue.pattern, &mut |place| {
668 + if !stable_sidemap.is_stable(place.identifier)
669 + && reactive_ids.contains(&place.identifier)
670 + {
671 + place.reactive = true;
672 + }
673 + });
674 }
675 InstructionValue::PrefixUpdate { lvalue, .. }
676 | InstructionValue::PostfixUpdate { lvalue, .. } => {
compiler/crates/react_compiler_inference/src/infer_reactive_scope_variables.rs
+28 -38
@@ -21,8 +21,8 @@ use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
21 use react_compiler_hir::environment::Environment;
22 use react_compiler_hir::visitors;
23 use react_compiler_hir::{
24 - DeclarationId, EvaluationOrder, HirFunction, IdentifierId,
25 - InstructionValue, Pattern, Position, SourceLocation,
24 + DeclarationId, EvaluationOrder, HirFunction, IdentifierId, InstructionValue, Pattern, Position,
25 + SourceLocation,
26 };
27 use react_compiler_utils::DisjointSet;
28
@@ -36,7 +36,10 @@ use react_compiler_utils::DisjointSet;
36 /// variable. Variables that co-mutate are assigned to the same reactive scope.
37 ///
38 /// Corresponds to TS `inferReactiveScopeVariables(fn: HIRFunction): void`.
39 -pub fn infer_reactive_scope_variables(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
39 +pub fn infer_reactive_scope_variables(
40 + func: &mut HirFunction,
41 + env: &mut Environment,
42 +) -> Result<(), CompilerDiagnostic> {
43 // Phase 1: find disjoint sets of co-mutating identifiers
44 let mut scope_identifiers = find_disjoint_mutable_values(func, env);
45
@@ -45,7 +48,9 @@ pub fn infer_reactive_scope_variables(func: &mut HirFunction, env: &mut Environm
48 let mut scopes: HashMap<IdentifierId, ScopeState> = HashMap::new();
49
50 scope_identifiers.for_each(|identifier_id, group_id| {
48 - let ident_range = env.identifiers[identifier_id.0 as usize].mutable_range.clone();
51 + let ident_range = env.identifiers[identifier_id.0 as usize]
52 + .mutable_range
53 + .clone();
54 let ident_loc = env.identifiers[identifier_id.0 as usize].loc;
55
56 let state = scopes.entry(group_id).or_insert_with(|| {
@@ -67,8 +72,7 @@ pub fn infer_reactive_scope_variables(func: &mut HirFunction, env: &mut Environm
72 if scope.range.start == EvaluationOrder(0) {
73 scope.range.start = ident_range.start;
74 } else if ident_range.start != EvaluationOrder(0) {
70 - scope.range.start =
71 - EvaluationOrder(scope.range.start.0.min(ident_range.start.0));
75 + scope.range.start = EvaluationOrder(scope.range.start.0.min(ident_range.start.0));
76 }
77 scope.range.end = EvaluationOrder(scope.range.end.0.max(ident_range.end.0));
78 }
@@ -105,7 +109,8 @@ pub fn infer_reactive_scope_variables(func: &mut HirFunction, env: &mut Environm
109 let instr = &func.instructions[instr_id.0 as usize];
110 max_instruction = EvaluationOrder(max_instruction.0.max(instr.id.0));
111 }
108 - max_instruction = EvaluationOrder(max_instruction.0.max(block.terminal.evaluation_order().0));
112 + max_instruction =
113 + EvaluationOrder(max_instruction.0.max(block.terminal.evaluation_order().0));
114 }
115
116 for (_group_id, state) in &scopes {
@@ -139,10 +144,7 @@ struct ScopeState {
144
145 /// Merge two source locations, preferring non-None values.
146 /// Corresponds to TS `mergeLocation`.
142 -fn merge_location(
143 - l: Option<SourceLocation>,
144 - r: Option<SourceLocation>,
145 -) -> Option<SourceLocation> {
147 +fn merge_location(l: Option<SourceLocation>, r: Option<SourceLocation>) -> Option<SourceLocation> {
148 match (l, r) {
149 (None, r) => r,
150 (l, None) => l,
@@ -171,7 +173,6 @@ fn merge_location(
173 // is_mutable / in_range helpers
174 // =============================================================================
175
174 -
176 // =============================================================================
177 // may_allocate
178 // =============================================================================
@@ -261,7 +262,10 @@ fn each_instruction_value_operand(
262 /// Find disjoint sets of co-mutating identifier IDs.
263 ///
264 /// Corresponds to TS `findDisjointMutableValues(fn: HIRFunction): DisjointSet<Identifier>`.
264 -pub(crate) fn find_disjoint_mutable_values(func: &HirFunction, env: &Environment) -> DisjointSet<IdentifierId> {
265 +pub(crate) fn find_disjoint_mutable_values(
266 + func: &HirFunction,
267 + env: &Environment,
268 +) -> DisjointSet<IdentifierId> {
269 let mut scope_identifiers = DisjointSet::<IdentifierId>::new();
270 let mut declarations: HashMap<DeclarationId, IdentifierId> = HashMap::new();
271
@@ -280,9 +284,7 @@ pub(crate) fn find_disjoint_mutable_values(func: &HirFunction, env: &Environment
284 .map(|iid| func.instructions[iid.0 as usize].id)
285 .unwrap_or(block.terminal.evaluation_order());
286
283 - if phi_range.start.0 + 1 != phi_range.end.0
284 - && phi_range.end > first_instr_id
285 - {
287 + if phi_range.start.0 + 1 != phi_range.end.0 && phi_range.end > first_instr_id {
288 let mut operands = vec![phi_id];
289 if let Some(&decl_id) = declarations.get(&phi_decl_id) {
290 operands.push(decl_id);
@@ -327,17 +329,13 @@ pub(crate) fn find_disjoint_mutable_values(func: &HirFunction, env: &Environment
329 let decl_id = env.identifiers[place_id.0 as usize].declaration_id;
330 declarations.entry(decl_id).or_insert(place_id);
331
330 - let place_range =
331 - &env.identifiers[place_id.0 as usize].mutable_range;
332 + let place_range = &env.identifiers[place_id.0 as usize].mutable_range;
333 if place_range.end.0 > place_range.start.0 + 1 {
334 operands.push(place_id);
335 }
336
336 - let value_range =
337 - &env.identifiers[value.identifier.0 as usize].mutable_range;
338 - if value_range.contains(instr.id)
339 - && value_range.start.0 > 0
340 - {
337 + let value_range = &env.identifiers[value.identifier.0 as usize].mutable_range;
338 + if value_range.contains(instr.id) && value_range.start.0 > 0 {
339 operands.push(value.identifier);
340 }
341 }
@@ -347,28 +345,22 @@ pub(crate) fn find_disjoint_mutable_values(func: &HirFunction, env: &Environment
345 let decl_id = env.identifiers[place_id.0 as usize].declaration_id;
346 declarations.entry(decl_id).or_insert(*place_id);
347
350 - let place_range =
351 - &env.identifiers[place_id.0 as usize].mutable_range;
348 + let place_range = &env.identifiers[place_id.0 as usize].mutable_range;
349 if place_range.end.0 > place_range.start.0 + 1 {
350 operands.push(*place_id);
351 }
352 }
353
357 - let value_range =
358 - &env.identifiers[value.identifier.0 as usize].mutable_range;
359 - if value_range.contains(instr.id)
360 - && value_range.start.0 > 0
361 - {
354 + let value_range = &env.identifiers[value.identifier.0 as usize].mutable_range;
355 + if value_range.contains(instr.id) && value_range.start.0 > 0 {
356 operands.push(value.identifier);
357 }
358 }
359 InstructionValue::MethodCall { property, .. } => {
360 // For MethodCall: include all mutable operands plus the computed property
367 - let all_operands =
368 - each_instruction_value_operand(&instr.value, env);
361 + let all_operands = each_instruction_value_operand(&instr.value, env);
362 for op_id in &all_operands {
370 - let op_range =
371 - &env.identifiers[op_id.0 as usize].mutable_range;
363 + let op_range = &env.identifiers[op_id.0 as usize].mutable_range;
364 if op_range.contains(instr.id) && op_range.start.0 > 0 {
365 operands.push(*op_id);
366 }
@@ -378,11 +370,9 @@ pub(crate) fn find_disjoint_mutable_values(func: &HirFunction, env: &Environment
370 }
371 _ => {
372 // For all other instructions: include mutable operands
381 - let all_operands =
382 - each_instruction_value_operand(&instr.value, env);
373 + let all_operands = each_instruction_value_operand(&instr.value, env);
374 for op_id in &all_operands {
384 - let op_range =
385 - &env.identifiers[op_id.0 as usize].mutable_range;
375 + let op_range = &env.identifiers[op_id.0 as usize].mutable_range;
376 if op_range.contains(instr.id) && op_range.start.0 > 0 {
377 operands.push(*op_id);
378 }
compiler/crates/react_compiler_inference/src/lib.rs
+2 -2
@@ -1,10 +1,10 @@
1 pub mod align_method_call_scopes;
2 pub mod align_object_method_scopes;
3 pub mod align_reactive_scopes_to_block_scopes_hir;
4 +pub mod analyse_functions;
5 pub mod build_reactive_scope_terminals_hir;
6 pub mod flatten_reactive_loops_hir;
7 pub mod flatten_scopes_with_hooks_or_use_hir;
7 -pub mod analyse_functions;
8 pub mod infer_mutation_aliasing_effects;
9 pub mod infer_mutation_aliasing_ranges;
10 pub mod infer_reactive_places;
@@ -16,10 +16,10 @@ pub mod propagate_scope_dependencies_hir;
16 pub use align_method_call_scopes::align_method_call_scopes;
17 pub use align_object_method_scopes::align_object_method_scopes;
18 pub use align_reactive_scopes_to_block_scopes_hir::align_reactive_scopes_to_block_scopes_hir;
19 +pub use analyse_functions::analyse_functions;
20 pub use build_reactive_scope_terminals_hir::build_reactive_scope_terminals_hir;
21 pub use flatten_reactive_loops_hir::flatten_reactive_loops_hir;
22 pub use flatten_scopes_with_hooks_or_use_hir::flatten_scopes_with_hooks_or_use_hir;
22 -pub use analyse_functions::analyse_functions;
23 pub use infer_mutation_aliasing_effects::infer_mutation_aliasing_effects;
24 pub use infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges;
25 pub use infer_reactive_places::infer_reactive_places;
compiler/crates/react_compiler_inference/src/memoize_fbt_and_macro_operands_in_same_scope.rs
+10 -15
@@ -18,8 +18,7 @@ use std::collections::{HashMap, HashSet};
18 use react_compiler_hir::environment::Environment;
19 use react_compiler_hir::visitors;
20 use react_compiler_hir::{
21 - HirFunction, IdentifierId, InstructionValue, JsxTag,
22 - PrimitiveValue, PropertyLiteral, ScopeId,
21 + HirFunction, IdentifierId, InstructionValue, JsxTag, PrimitiveValue, PropertyLiteral, ScopeId,
22 };
23
24 /// Whether a macro requires its arguments to be transitively inlined (e.g., fbt)
@@ -74,7 +73,10 @@ fn fbt_macro() -> MacroDefinition {
73 p
74 }),
75 };
77 - fbt.properties.as_mut().unwrap().insert("enum".to_string(), enum_macro);
76 + fbt.properties
77 + .as_mut()
78 + .unwrap()
79 + .insert("enum".to_string(), enum_macro);
80 fbt
81 }
82
@@ -150,9 +152,8 @@ fn populate_macro_tags(
152 if let PropertyLiteral::String(prop_name) = property {
153 if let Some(macro_def) = macro_tags.get(&object.identifier).cloned() {
154 let property_macro = if let Some(ref props) = macro_def.properties {
153 - let prop_def = props
154 - .get(prop_name.as_str())
155 - .or_else(|| props.get("*"));
155 + let prop_def =
156 + props.get(prop_name.as_str()).or_else(|| props.get("*"));
157 match prop_def {
158 Some(def) => def.clone(),
159 None => macro_def.clone(),
@@ -240,16 +241,11 @@ fn merge_macro_arguments(
241 };
242
243 let macro_def = match tag {
243 - JsxTag::Place(place) => {
244 - macro_tags.get(&place.identifier).cloned()
245 - }
246 - JsxTag::Builtin(builtin) => {
247 - macro_kinds.get(builtin.name.as_str()).cloned()
248 - }
244 + JsxTag::Place(place) => macro_tags.get(&place.identifier).cloned(),
245 + JsxTag::Builtin(builtin) => macro_kinds.get(builtin.name.as_str()).cloned(),
246 };
247
251 - let macro_def = macro_def
252 - .or_else(|| macro_tags.get(&lvalue_id).cloned());
248 + let macro_def = macro_def.or_else(|| macro_tags.get(&lvalue_id).cloned());
249
250 if let Some(macro_def) = macro_def {
251 visit_operands(
@@ -378,4 +374,3 @@ fn visit_operands(
374 macro_values.insert(operand_id);
375 }
376 }
381 -
compiler/crates/react_compiler_inference/src/merge_overlapping_reactive_scopes_hir.rs
+19 -23
@@ -99,25 +99,21 @@ fn collect_scope_info(func: &HirFunction, env: &Environment) -> ScopeInfo {
99 let mut scope_ends_map: HashMap<EvaluationOrder, Vec<ScopeId>> = HashMap::new();
100 let mut place_scopes: HashMap<IdentifierId, ScopeId> = HashMap::new();
101
102 - let mut collect_place_scope =
103 - |identifier_id: IdentifierId, env: &Environment| {
104 - let scope_id = match env.identifiers[identifier_id.0 as usize].scope {
105 - Some(s) => s,
106 - None => return,
107 - };
108 - place_scopes.insert(identifier_id, scope_id);
109 - let range = &env.scopes[scope_id.0 as usize].range;
110 - if range.start != range.end {
111 - scope_starts_map
112 - .entry(range.start)
113 - .or_default()
114 - .push(scope_id);
115 - scope_ends_map
116 - .entry(range.end)
117 - .or_default()
118 - .push(scope_id);
119 - }
102 + let mut collect_place_scope = |identifier_id: IdentifierId, env: &Environment| {
103 + let scope_id = match env.identifiers[identifier_id.0 as usize].scope {
104 + Some(s) => s,
105 + None => return,
106 };
107 + place_scopes.insert(identifier_id, scope_id);
108 + let range = &env.scopes[scope_id.0 as usize].range;
109 + if range.start != range.end {
110 + scope_starts_map
111 + .entry(range.start)
112 + .or_default()
113 + .push(scope_id);
114 + scope_ends_map.entry(range.end).or_default().push(scope_id);
115 + }
116 + };
117
118 for (_block_id, block) in &func.body.blocks {
119 for &instr_id in &block.instructions {
@@ -292,8 +288,7 @@ fn get_overlapping_reactive_scopes(
288 // Visit operands
289 let is_func_or_method = matches!(
290 &instr.value,
295 - InstructionValue::FunctionExpression { .. }
296 - | InstructionValue::ObjectMethod { .. }
291 + InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. }
292 );
293 let operand_ids = each_instruction_operand_ids_with_types(instr, env);
294 for (op_id, type_) in &operand_ids {
@@ -353,7 +348,8 @@ pub fn merge_overlapping_reactive_scopes_hir(func: &mut HirFunction, env: &mut E
348 // When scope.range is updated, ALL identifiers referencing that range object
349 // automatically see the new values. We use MutableRangeId to identify which
350 // identifiers share the same logical range as a root scope.
356 - let mut original_root_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> = HashMap::new();
351 + let mut original_root_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> =
352 + HashMap::new();
353 for (_, root_id) in &scope_groups {
354 if !original_root_range_ids.contains_key(root_id) {
355 let range_id = env.scopes[root_id.0 as usize].range.id;
@@ -410,9 +406,9 @@ fn each_instruction_operand_ids_with_types(
406 visitors::each_instruction_operand(instr, env)
407 .into_iter()
408 .map(|p| {
413 - let type_ = env.types[env.identifiers[p.identifier.0 as usize].type_.0 as usize].clone();
409 + let type_ =
410 + env.types[env.identifiers[p.identifier.0 as usize].type_.0 as usize].clone();
411 (p.identifier, type_)
412 })
413 .collect()
414 }
418 -
compiler/crates/react_compiler_inference/src/propagate_scope_dependencies_hir.rs
+225 -194
@@ -12,18 +12,17 @@
12 //! - `src/HIR/CollectHoistablePropertyLoads.ts`
13 //! - `src/HIR/DeriveMinimalDependenciesHIR.ts`
14
15 -use std::collections::{BTreeSet, HashMap, HashSet};
15 use indexmap::IndexMap;
16 +use std::collections::{BTreeSet, HashMap, HashSet};
17
18 use react_compiler_hir::environment::Environment;
19 +use react_compiler_hir::visitors::{ScopeBlockInfo, ScopeBlockTraversal};
20 use react_compiler_hir::{
20 - BasicBlock, BlockId, DeclarationId, DependencyPathEntry, EvaluationOrder,
21 - FunctionId, GotoVariant, HirFunction, IdentifierId, Instruction, InstructionId,
22 - InstructionKind, InstructionValue, MutableRange, ParamPattern,
23 - Place, PlaceOrSpread, PropertyLiteral, ReactFunctionType, ReactiveScopeDependency,
24 - ScopeId, Terminal, Type, visitors,
21 + BasicBlock, BlockId, DeclarationId, DependencyPathEntry, EvaluationOrder, FunctionId,
22 + GotoVariant, HirFunction, IdentifierId, Instruction, InstructionId, InstructionKind,
23 + InstructionValue, MutableRange, ParamPattern, Place, PlaceOrSpread, PropertyLiteral,
24 + ReactFunctionType, ReactiveScopeDependency, ScopeId, Terminal, Type, visitors,
25 };
26 -use react_compiler_hir::visitors::{ScopeBlockTraversal, ScopeBlockInfo};
26
27 // =============================================================================
28 // Public entry point
@@ -42,11 +41,17 @@ pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Enviro
41 } = collect_optional_chain_sidemap(func, env);
42
43 let hoistable_property_loads = {
45 - let (working, registry) = collect_hoistable_and_propagate(func, env, &temporaries, &hoistable_objects);
44 + let (working, registry) =
45 + collect_hoistable_and_propagate(func, env, &temporaries, &hoistable_objects);
46 // Convert to scope-keyed map with full dependency paths
47 let mut keyed: HashMap<ScopeId, Vec<ReactiveScopeDependency>> = HashMap::new();
48 for (_block_id, block) in &func.body.blocks {
49 - if let Terminal::Scope { scope, block: inner_block, .. } = &block.terminal {
49 + if let Terminal::Scope {
50 + scope,
51 + block: inner_block,
52 + ..
53 + } = &block.terminal
54 + {
55 if let Some(node_indices) = working.get(inner_block) {
56 let deps: Vec<ReactiveScopeDependency> = node_indices
57 .iter()
@@ -80,15 +85,11 @@ pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Enviro
85 }
86
87 let hoistables = hoistable_property_loads.get(scope_id);
83 - let hoistables = hoistables.expect(
84 - "[PropagateScopeDependencies] Scope not found in tracked blocks",
85 - );
88 + let hoistables =
89 + hoistables.expect("[PropagateScopeDependencies] Scope not found in tracked blocks");
90
91 // Step 2: Calculate hoistable dependencies using the tree.
88 - let mut tree = ReactiveScopeDependencyTreeHIR::new(
89 - hoistables.iter(),
90 - env,
91 - );
92 + let mut tree = ReactiveScopeDependencyTreeHIR::new(hoistables.iter(), env);
93 for dep in deps {
94 tree.add_dependency(dep.clone(), env);
95 }
@@ -98,8 +99,10 @@ pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Enviro
99 let scope = &mut env.scopes[scope_id.0 as usize];
100 for candidate_dep in candidates {
101 let already_exists = scope.dependencies.iter().any(|existing_dep| {
101 - let existing_decl_id = env.identifiers[existing_dep.identifier.0 as usize].declaration_id;
102 - let candidate_decl_id = env.identifiers[candidate_dep.identifier.0 as usize].declaration_id;
102 + let existing_decl_id =
103 + env.identifiers[existing_dep.identifier.0 as usize].declaration_id;
104 + let candidate_decl_id =
105 + env.identifiers[candidate_dep.identifier.0 as usize].declaration_id;
106 existing_decl_id == candidate_decl_id
107 && are_equal_paths(&existing_dep.path, &candidate_dep.path)
108 });
@@ -112,9 +115,9 @@ pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Enviro
115
116 fn are_equal_paths(a: &[DependencyPathEntry], b: &[DependencyPathEntry]) -> bool {
117 a.len() == b.len()
115 - && a.iter().zip(b.iter()).all(|(ai, bi)| {
116 - ai.property == bi.property && ai.optional == bi.optional
117 - })
118 + && a.iter()
119 + .zip(b.iter())
120 + .all(|(ai, bi)| ai.property == bi.property && ai.optional == bi.optional)
121 }
122
123 // =============================================================================
@@ -139,7 +142,9 @@ fn find_temporaries_used_outside_declaring_scope(
142 env: &Environment| {
143 let decl_id = env.identifiers[place_id.0 as usize].declaration_id;
144 if let Some(&declaring_scope) = declarations.get(&decl_id) {
142 - if !traversal.is_scope_active(declaring_scope) && !pruned_scopes.contains(&declaring_scope) {
145 + if !traversal.is_scope_active(declaring_scope)
146 + && !pruned_scopes.contains(&declaring_scope)
147 + {
148 used_outside.insert(decl_id);
149 }
150 }
@@ -150,14 +155,23 @@ fn find_temporaries_used_outside_declaring_scope(
155 traversal.record_scopes(block);
156
157 let scope_start_info = traversal.block_infos.get(block_id);
153 - if let Some(ScopeBlockInfo::Begin { scope, pruned: true, .. }) = scope_start_info {
158 + if let Some(ScopeBlockInfo::Begin {
159 + scope,
160 + pruned: true,
161 + ..
162 + }) = scope_start_info
163 + {
164 pruned_scopes.insert(*scope);
165 }
166
167 for &instr_id in &block.instructions {
168 let instr = &func.instructions[instr_id.0 as usize];
169 // Handle operands
160 - for op_id in visitors::each_instruction_operand(instr, env).into_iter().map(|p| p.identifier).collect::<Vec<_>>() {
170 + for op_id in visitors::each_instruction_operand(instr, env)
171 + .into_iter()
172 + .map(|p| p.identifier)
173 + .collect::<Vec<_>>()
174 + {
175 handle_place(
176 op_id,
177 &declarations,
@@ -175,7 +189,8 @@ fn find_temporaries_used_outside_declaring_scope(
189 InstructionValue::LoadLocal { .. }
190 | InstructionValue::LoadContext { .. }
191 | InstructionValue::PropertyLoad { .. } => {
178 - let decl_id = env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id;
192 + let decl_id =
193 + env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id;
194 declarations.insert(decl_id, scope);
195 }
196 _ => {}
@@ -185,7 +200,11 @@ fn find_temporaries_used_outside_declaring_scope(
200 }
201
202 // Terminal operands
188 - for op_id in visitors::each_terminal_operand(&block.terminal).into_iter().map(|p| p.identifier).collect::<Vec<_>>() {
203 + for op_id in visitors::each_terminal_operand(&block.terminal)
204 + .into_iter()
205 + .map(|p| p.identifier)
206 + .collect::<Vec<_>>()
207 + {
208 handle_place(
209 op_id,
210 &declarations,
@@ -267,17 +286,20 @@ fn collect_temporaries_sidemap_impl(
286
287 match &instr.value {
288 InstructionValue::PropertyLoad {
270 - object, property, loc, ..
289 + object,
290 + property,
291 + loc,
292 + ..
293 } if !used_outside => {
272 - if inner_fn_context.is_none()
273 - || temporaries.contains_key(&object.identifier)
274 - {
294 + if inner_fn_context.is_none() || temporaries.contains_key(&object.identifier) {
295 let prop = get_property(object, property, false, *loc, temporaries, env);
296 temporaries.insert(instr.lvalue.identifier, prop);
297 }
298 }
299 InstructionValue::LoadLocal { place, loc, .. }
280 - if env.identifiers[instr.lvalue.identifier.0 as usize].name.is_none()
300 + if env.identifiers[instr.lvalue.identifier.0 as usize]
301 + .name
302 + .is_none()
303 && env.identifiers[place.identifier.0 as usize].name.is_some()
304 && !used_outside =>
305 {
@@ -300,7 +322,9 @@ fn collect_temporaries_sidemap_impl(
322 }
323 value @ InstructionValue::LoadContext { place, loc, .. }
324 if is_load_context_mutable(value, instr_eval_order, env)
303 - && env.identifiers[instr.lvalue.identifier.0 as usize].name.is_none()
325 + && env.identifiers[instr.lvalue.identifier.0 as usize]
326 + .name
327 + .is_none()
328 && env.identifiers[place.identifier.0 as usize].name.is_some()
329 && !used_outside =>
330 {
@@ -397,10 +421,7 @@ enum ProcessedInstr {
421 Terminal(BlockId),
422 }
423
400 -fn collect_optional_chain_sidemap(
401 - func: &HirFunction,
402 - env: &Environment,
403 -) -> OptionalChainSidemap {
424 +fn collect_optional_chain_sidemap(func: &HirFunction, env: &Environment) -> OptionalChainSidemap {
425 let mut ctx = OptionalTraversalContext {
426 seen_optionals: HashSet::new(),
427 processed_instrs_in_optional: HashSet::new(),
@@ -581,7 +602,10 @@ fn traverse_optional_block(
602 &func.instructions[maybe_test_block.instructions[i - 1].0 as usize];
603 match &curr_instr.value {
604 InstructionValue::PropertyLoad {
584 - object, property, loc, ..
605 + object,
606 + property,
607 + loc,
608 + ..
609 } if object.identifier == prev_instr.lvalue.identifier => {
610 path.push(DependencyPathEntry {
611 property: property.clone(),
@@ -699,7 +723,9 @@ fn traverse_optional_block(
723 };
724
725 ctx.processed_instrs_in_optional
702 - .insert(ProcessedInstr::Instruction(match_result.store_local_lvalue_id));
726 + .insert(ProcessedInstr::Instruction(
727 + match_result.store_local_lvalue_id,
728 + ));
729 ctx.processed_instrs_in_optional
730 .insert(ProcessedInstr::Terminal(match &test_terminal {
731 Terminal::Branch { .. } => {
@@ -759,7 +785,7 @@ fn traverse_optional_block(
785
786 #[derive(Debug, Clone)]
787 struct PropertyPathNode {
762 - properties: HashMap<PropertyLiteral, usize>, // index into registry
788 + properties: HashMap<PropertyLiteral, usize>, // index into registry
789 optional_properties: HashMap<PropertyLiteral, usize>, // index into registry
790 #[allow(dead_code)]
791 parent: Option<usize>,
@@ -816,7 +842,10 @@ impl PropertyPathRegistry {
842 ) -> usize {
843 let map_key = entry.property.clone();
844 let existing = if entry.optional {
819 - self.nodes[parent_idx].optional_properties.get(&map_key).copied()
845 + self.nodes[parent_idx]
846 + .optional_properties
847 + .get(&map_key)
848 + .copied()
849 } else {
850 self.nodes[parent_idx].properties.get(&map_key).copied()
851 };
@@ -868,10 +897,7 @@ impl PropertyPathRegistry {
897 /// `<base>.PROPERTY`.
898 ///
899 /// Port of `reduceMaybeOptionalChains` from CollectHoistablePropertyLoads.ts.
871 -fn reduce_maybe_optional_chains(
872 - nodes: &mut BTreeSet<usize>,
873 - registry: &mut PropertyPathRegistry,
874 -) {
900 +fn reduce_maybe_optional_chains(nodes: &mut BTreeSet<usize>, registry: &mut PropertyPathRegistry) {
901 // Collect indices of nodes that have optional in their path
902 let mut optional_chain_nodes: BTreeSet<usize> = nodes
903 .iter()
@@ -940,7 +966,8 @@ fn collect_hoistable_property_loads(
966 hoistable_from_optionals: &HashMap<BlockId, ReactiveScopeDependency>,
967 ) -> HashMap<BlockId, BlockInfo> {
968 let mut registry = PropertyPathRegistry::new();
943 - let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type == ReactFunctionType::Component
969 + let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type
970 + == ReactFunctionType::Component
971 || func.fn_type == ReactFunctionType::Hook
972 {
973 func.params
@@ -984,7 +1011,8 @@ fn is_immutable_at_instr(
1011 return nested_ctx.contains(&identifier_id);
1012 }
1013 let ident = &env.identifiers[identifier_id.0 as usize];
987 - let mutable_at_instr = ident.mutable_range.end > EvaluationOrder(ident.mutable_range.start.0 + 1)
1014 + let mutable_at_instr = ident.mutable_range.end
1015 + > EvaluationOrder(ident.mutable_range.start.0 + 1)
1016 && ident.scope.is_some()
1017 && {
1018 let scope = &env.scopes[ident.scope.unwrap().0 as usize];
@@ -1002,19 +1030,17 @@ fn get_maybe_non_null_in_instruction(
1030 temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>,
1031 ) -> Option<ReactiveScopeDependency> {
1032 match value {
1005 - InstructionValue::PropertyLoad { object, .. } => {
1006 - Some(
1007 - temporaries
1008 - .get(&object.identifier)
1009 - .cloned()
1010 - .unwrap_or_else(|| ReactiveScopeDependency {
1011 - identifier: object.identifier,
1012 - reactive: object.reactive,
1013 - path: vec![],
1014 - loc: object.loc,
1015 - }),
1016 - )
1017 - }
1033 + InstructionValue::PropertyLoad { object, .. } => Some(
1034 + temporaries
1035 + .get(&object.identifier)
1036 + .cloned()
1037 + .unwrap_or_else(|| ReactiveScopeDependency {
1038 + identifier: object.identifier,
1039 + reactive: object.reactive,
1040 + path: vec![],
1041 + loc: object.loc,
1042 + }),
1043 + ),
1044 InstructionValue::Destructure { value: val, .. } => {
1045 temporaries.get(&val.identifier).cloned()
1046 }
@@ -1037,7 +1063,14 @@ fn collect_hoistable_property_loads_impl(
1063 // Return the propagated results, converting HashSet<usize> back to BlockInfo
1064 working
1065 .into_iter()
1040 - .map(|(k, v)| (k, BlockInfo { assumed_non_null_objects: v }))
1066 + .map(|(k, v)| {
1067 + (
1068 + k,
1069 + BlockInfo {
1070 + assumed_non_null_objects: v,
1071 + },
1072 + )
1073 + })
1074 .collect()
1075 }
1076
@@ -1045,10 +1078,7 @@ fn collect_hoistable_property_loads_impl(
1078 /// Returns the set of LoweredFunction FunctionIds that are assumed to be invoked.
1079 /// The `temporaries` map is shared across recursive calls (matching TS behavior where
1080 /// the same Map is passed to recursive invocations for inner functions).
1048 -fn get_assumed_invoked_functions(
1049 - func: &HirFunction,
1050 - env: &Environment,
1051 -) -> HashSet<FunctionId> {
1081 +fn get_assumed_invoked_functions(func: &HirFunction, env: &Environment) -> HashSet<FunctionId> {
1082 let mut temporaries: HashMap<IdentifierId, (FunctionId, HashSet<FunctionId>)> = HashMap::new();
1083 get_assumed_invoked_functions_impl(func, env, &mut temporaries)
1084 }
@@ -1066,12 +1096,12 @@ fn get_assumed_invoked_functions_impl(
1096 let instr = &func.instructions[instr_id.0 as usize];
1097 match &instr.value {
1098 InstructionValue::FunctionExpression { lowered_func, .. } => {
1069 - temporaries.insert(
1070 - instr.lvalue.identifier,
1071 - (lowered_func.func, HashSet::new()),
1072 - );
1099 + temporaries
1100 + .insert(instr.lvalue.identifier, (lowered_func.func, HashSet::new()));
1101 }
1074 - InstructionValue::StoreLocal { value: val, lvalue, .. } => {
1102 + InstructionValue::StoreLocal {
1103 + value: val, lvalue, ..
1104 + } => {
1105 if let Some(entry) = temporaries.get(&val.identifier).cloned() {
1106 temporaries.insert(lvalue.place.identifier, entry);
1107 }
@@ -1092,7 +1122,8 @@ fn get_assumed_invoked_functions_impl(
1122 let instr = &func.instructions[instr_id.0 as usize];
1123 match &instr.value {
1124 InstructionValue::CallExpression { callee, args, .. } => {
1095 - let callee_ty = &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
1125 + let callee_ty =
1126 + &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
1127 let maybe_hook = env.get_hook_kind_for_type(callee_ty).ok().flatten();
1128 if let Some(entry) = temporaries.get(&callee.identifier) {
1129 // Direct calls
@@ -1108,7 +1139,9 @@ fn get_assumed_invoked_functions_impl(
1139 }
1140 }
1141 }
1111 - InstructionValue::JsxExpression { props, children, .. } => {
1142 + InstructionValue::JsxExpression {
1143 + props, children, ..
1144 + } => {
1145 // Assume JSX attributes and children are safe to invoke
1146 for prop in props {
1147 if let react_compiler_hir::JsxAttribute::Attribute { place, .. } = prop {
@@ -1136,7 +1169,8 @@ fn get_assumed_invoked_functions_impl(
1169 // Recursively traverse into other function expressions
1170 // TS passes the shared temporaries map to the recursive call
1171 let inner_func = &env.functions[lowered_func.func.0 as usize];
1139 - let lambdas_called = get_assumed_invoked_functions_impl(inner_func, env, temporaries);
1172 + let lambdas_called =
1173 + get_assumed_invoked_functions_impl(inner_func, env, temporaries);
1174 if let Some(entry) = temporaries.get_mut(&instr.lvalue.identifier) {
1175 for called in lambdas_called {
1176 entry.1.insert(called);
@@ -1174,7 +1208,9 @@ fn get_assumed_invoked_functions_impl(
1208 changed = true;
1209 hoistable.insert(id);
1210 }
1177 - if !changed { break; }
1211 + if !changed {
1212 + break;
1213 + }
1214 }
1215
1216 hoistable
@@ -1188,15 +1224,9 @@ fn collect_non_nulls_in_blocks(
1224 ) -> HashMap<BlockId, BlockInfo> {
1225 // Known non-null identifiers (e.g. component props)
1226 let mut known_non_null: BTreeSet<usize> = BTreeSet::new();
1191 - if func.fn_type == ReactFunctionType::Component
1192 - && !func.params.is_empty()
1193 - {
1227 + if func.fn_type == ReactFunctionType::Component && !func.params.is_empty() {
1228 if let ParamPattern::Place(place) = &func.params[0] {
1195 - let node_idx = registry.get_or_create_identifier(
1196 - place.identifier,
1197 - true,
1198 - place.loc,
1199 - );
1229 + let node_idx = registry.get_or_create_identifier(place.identifier, true, place.loc);
1230 known_non_null.insert(node_idx);
1231 }
1232 }
@@ -1224,9 +1254,16 @@ fn collect_non_nulls_in_blocks(
1254
1255 // Handle StartMemoize deps for enablePreserveExistingMemoizationGuarantees
1256 if env.enable_preserve_existing_memoization_guarantees {
1227 - if let InstructionValue::StartMemoize { deps: Some(deps), .. } = &instr.value {
1257 + if let InstructionValue::StartMemoize {
1258 + deps: Some(deps), ..
1259 + } = &instr.value
1260 + {
1261 for dep in deps {
1229 - if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal { value: val, .. } = &dep.root {
1262 + if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {
1263 + value: val,
1264 + ..
1265 + } = &dep.root
1266 + {
1267 if !is_immutable_at_instr(val.identifier, instr.id, env, ctx) {
1268 continue;
1269 }
@@ -1253,17 +1290,20 @@ fn collect_non_nulls_in_blocks(
1290 if ctx.assumed_invoked_fns.contains(&lowered_func.func) {
1291 let inner_func = &env.functions[lowered_func.func.0 as usize];
1292 // Build nested fn immutable context
1256 - let nested_fn_immutable_context: HashSet<IdentifierId> = if ctx.nested_fn_immutable_context.is_some() {
1257 - // Already in a nested fn context, use existing
1258 - ctx.nested_fn_immutable_context.unwrap().clone()
1259 - } else {
1260 - inner_func
1261 - .context
1262 - .iter()
1263 - .filter(|place| is_immutable_at_instr(place.identifier, instr.id, env, ctx))
1264 - .map(|place| place.identifier)
1265 - .collect()
1266 - };
1293 + let nested_fn_immutable_context: HashSet<IdentifierId> =
1294 + if ctx.nested_fn_immutable_context.is_some() {
1295 + // Already in a nested fn context, use existing
1296 + ctx.nested_fn_immutable_context.unwrap().clone()
1297 + } else {
1298 + inner_func
1299 + .context
1300 + .iter()
1301 + .filter(|place| {
1302 + is_immutable_at_instr(place.identifier, instr.id, env, ctx)
1303 + })
1304 + .map(|place| place.identifier)
1305 + .collect()
1306 + };
1307 let inner_assumed = get_assumed_invoked_functions(inner_func, env);
1308 let inner_ctx = CollectHoistableContext {
1309 temporaries: ctx.temporaries,
@@ -1272,7 +1312,8 @@ fn collect_non_nulls_in_blocks(
1312 nested_fn_immutable_context: Some(&nested_fn_immutable_context),
1313 assumed_invoked_fns: &inner_assumed,
1314 };
1275 - let inner_nodes = collect_non_nulls_in_blocks(inner_func, env, &inner_ctx, registry);
1315 + let inner_nodes =
1316 + collect_non_nulls_in_blocks(inner_func, env, &inner_ctx, registry);
1317 // Propagate non-null from inner function
1318 let inner_working = propagate_non_null(inner_func, &inner_nodes, registry);
1319 // Get hoistables from inner function's entry block (after propagation)
@@ -1315,10 +1356,7 @@ fn propagate_non_null(
1356 let mut block_successors: HashMap<BlockId, BTreeSet<BlockId>> = HashMap::new();
1357 for (block_id, block) in &func.body.blocks {
1358 for pred in &block.preds {
1318 - block_successors
1319 - .entry(*pred)
1320 - .or_default()
1321 - .insert(*block_id);
1359 + block_successors.entry(*pred).or_default().insert(*block_id);
1360 }
1361 }
1362
@@ -1401,19 +1439,16 @@ fn recursively_propagate_non_null(
1439 traversal_state.insert(node_id, TraversalState::Active);
1440
1441 let neighbors: Vec<BlockId> = match direction {
1404 - PropagationDirection::Backward => {
1405 - block_successors
1406 - .get(&node_id)
1407 - .map(|s| s.iter().copied().collect())
1408 - .unwrap_or_default()
1409 - }
1410 - PropagationDirection::Forward => {
1411 - func.body
1412 - .blocks
1413 - .get(&node_id)
1414 - .map(|b| b.preds.iter().copied().collect())
1415 - .unwrap_or_default()
1416 - }
1442 + PropagationDirection::Backward => block_successors
1443 + .get(&node_id)
1444 + .map(|s| s.iter().copied().collect())
1445 + .unwrap_or_default(),
1446 + PropagationDirection::Forward => func
1447 + .body
1448 + .blocks
1449 + .get(&node_id)
1450 + .map(|b| b.preds.iter().copied().collect())
1451 + .unwrap_or_default(),
1452 };
1453
1454 let mut changed = false;
@@ -1448,7 +1483,10 @@ fn recursively_propagate_non_null(
1483 };
1484
1485 let prev_objects = working.get(&node_id).cloned().unwrap_or_default();
1451 - let mut merged: BTreeSet<usize> = prev_objects.union(&neighbor_intersection).copied().collect();
1486 + let mut merged: BTreeSet<usize> = prev_objects
1487 + .union(&neighbor_intersection)
1488 + .copied()
1489 + .collect();
1490 reduce_maybe_optional_chains(&mut merged, registry);
1491
1492 working.insert(node_id, merged.clone());
@@ -1467,7 +1505,8 @@ fn collect_hoistable_and_propagate(
1505 ) -> (HashMap<BlockId, BTreeSet<usize>>, PropertyPathRegistry) {
1506 let mut registry = PropertyPathRegistry::new();
1507 let assumed_invoked_fns = get_assumed_invoked_functions(func, env);
1470 - let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type == ReactFunctionType::Component
1508 + let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type
1509 + == ReactFunctionType::Component
1510 || func.fn_type == ReactFunctionType::Hook
1511 {
1512 func.params
@@ -1504,7 +1543,9 @@ fn key_by_scope_id(
1543 let mut keyed: HashMap<ScopeId, BlockInfo> = HashMap::new();
1544 for (_block_id, block) in &func.body.blocks {
1545 if let Terminal::Scope {
1507 - scope, block: inner_block, ..
1546 + scope,
1547 + block: inner_block,
1548 + ..
1549 } = &block.terminal
1550 {
1551 if let Some(info) = block_keyed.get(inner_block) {
@@ -1579,7 +1620,7 @@ struct DependencyNodeEntry {
1620
1621 struct ReactiveScopeDependencyTreeHIR {
1622 hoistable_roots: HashMap<IdentifierId, (HoistableNode, bool)>, // node + reactive
1582 - dep_roots: IndexMap<IdentifierId, (DependencyNode, bool)>, // node + reactive (preserves insertion order like JS Map)
1623 + dep_roots: IndexMap<IdentifierId, (DependencyNode, bool)>, // node + reactive (preserves insertion order like JS Map)
1624 }
1625
1626 impl ReactiveScopeDependencyTreeHIR {
@@ -1602,22 +1643,20 @@ impl ReactiveScopeDependencyTreeHIR {
1643 });
1644
1645 for dep in sorted_deps {
1605 - let root = hoistable_roots
1606 - .entry(dep.identifier)
1607 - .or_insert_with(|| {
1608 - let access_type = if !dep.path.is_empty() && dep.path[0].optional {
1609 - HoistableAccessType::Optional
1610 - } else {
1611 - HoistableAccessType::NonNull
1612 - };
1613 - (
1614 - HoistableNode {
1615 - properties: HashMap::new(),
1616 - access_type,
1617 - },
1618 - dep.reactive,
1619 - )
1620 - });
1646 + let root = hoistable_roots.entry(dep.identifier).or_insert_with(|| {
1647 + let access_type = if !dep.path.is_empty() && dep.path[0].optional {
1648 + HoistableAccessType::Optional
1649 + } else {
1650 + HoistableAccessType::NonNull
1651 + };
1652 + (
1653 + HoistableNode {
1654 + properties: HashMap::new(),
1655 + access_type,
1656 + },
1657 + dep.reactive,
1658 + )
1659 + });
1660
1661 let mut curr = &mut root.0;
1662 for i in 0..dep.path.len() {
@@ -1648,19 +1687,16 @@ impl ReactiveScopeDependencyTreeHIR {
1687 }
1688
1689 fn add_dependency(&mut self, dep: ReactiveScopeDependency, _env: &Environment) {
1651 - let root = self
1652 - .dep_roots
1653 - .entry(dep.identifier)
1654 - .or_insert_with(|| {
1655 - (
1656 - DependencyNode {
1657 - properties: IndexMap::new(),
1658 - access_type: PropertyAccessType::UnconditionalAccess,
1659 - loc: dep.loc,
1660 - },
1661 - dep.reactive,
1662 - )
1663 - });
1690 + let root = self.dep_roots.entry(dep.identifier).or_insert_with(|| {
1691 + (
1692 + DependencyNode {
1693 + properties: IndexMap::new(),
1694 + access_type: PropertyAccessType::UnconditionalAccess,
1695 + loc: dep.loc,
1696 + },
1697 + dep.reactive,
1698 + )
1699 + });
1700
1701 let mut dep_cursor = &mut root.0;
1702 let hoistable_cursor_root = self.hoistable_roots.get(&dep.identifier);
@@ -1671,9 +1707,8 @@ impl ReactiveScopeDependencyTreeHIR {
1707 let access_type: PropertyAccessType;
1708
1709 if entry.optional {
1674 - next_hoistable = hoistable_ptr.and_then(|h| {
1675 - h.properties.get(&entry.property).map(|e| &e.node)
1676 - });
1710 + next_hoistable =
1711 + hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node));
1712
1713 if hoistable_ptr.is_some()
1714 && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull
@@ -1685,9 +1720,8 @@ impl ReactiveScopeDependencyTreeHIR {
1720 } else if hoistable_ptr.is_some()
1721 && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull
1722 {
1688 - next_hoistable = hoistable_ptr.and_then(|h| {
1689 - h.properties.get(&entry.property).map(|e| &e.node)
1690 - });
1723 + next_hoistable =
1724 + hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node));
1725 access_type = PropertyAccessType::UnconditionalAccess;
1726 } else {
1727 // Break: truncate dependency
@@ -1714,20 +1748,16 @@ impl ReactiveScopeDependencyTreeHIR {
1748 }
1749
1750 // Mark final node as dependency
1717 - dep_cursor.access_type =
1718 - merge_access(dep_cursor.access_type, PropertyAccessType::OptionalDependency);
1751 + dep_cursor.access_type = merge_access(
1752 + dep_cursor.access_type,
1753 + PropertyAccessType::OptionalDependency,
1754 + );
1755 }
1756
1757 fn derive_minimal_dependencies(&self, _env: &Environment) -> Vec<ReactiveScopeDependency> {
1758 let mut results = Vec::new();
1759 for (&root_id, (root_node, reactive)) in &self.dep_roots {
1724 - collect_minimal_deps_in_subtree(
1725 - root_node,
1726 - *reactive,
1727 - root_id,
1728 - &[],
1729 - &mut results,
1730 - );
1760 + collect_minimal_deps_in_subtree(root_node, *reactive, root_id, &[], &mut results);
1761 }
1762 results
1763 }
@@ -1816,9 +1846,10 @@ impl<'a> DependencyCollectionContext<'a> {
1846 }
1847
1848 fn exit_scope(&mut self, scope_id: ScopeId, pruned: bool, env: &mut Environment) {
1819 - let scoped_deps = self.dep_stack.pop().expect(
1820 - "[PropagateScopeDeps]: Unexpected scope mismatch",
1821 - );
1849 + let scoped_deps = self
1850 + .dep_stack
1851 + .pop()
1852 + .expect("[PropagateScopeDeps]: Unexpected scope mismatch");
1853 self.scope_stack.pop();
1854
1855 // Propagate dependencies upward
@@ -2010,27 +2041,34 @@ fn visit_inner_function_blocks(
2041 ) {
2042 // Clone inner function's instructions and block structure to avoid
2043 // borrow conflicts when mutating env through handle_instruction.
2013 - let inner_instrs: Vec<Instruction> = env.functions[func_id.0 as usize]
2014 - .instructions
2015 - .clone();
2016 - let inner_blocks: Vec<(BlockId, Vec<InstructionId>, Vec<(BlockId, IdentifierId)>, Terminal)> =
2017 - env.functions[func_id.0 as usize]
2018 - .body
2019 - .blocks
2020 - .iter()
2021 - .map(|(bid, blk)| {
2022 - let phi_ops: Vec<(BlockId, IdentifierId)> = blk
2023 - .phis
2024 - .iter()
2025 - .flat_map(|phi| {
2026 - phi.operands
2027 - .iter()
2028 - .map(|(pred, place)| (*pred, place.identifier))
2029 - })
2030 - .collect();
2031 - (*bid, blk.instructions.clone(), phi_ops, blk.terminal.clone())
2032 - })
2033 - .collect();
2044 + let inner_instrs: Vec<Instruction> = env.functions[func_id.0 as usize].instructions.clone();
2045 + let inner_blocks: Vec<(
2046 + BlockId,
2047 + Vec<InstructionId>,
2048 + Vec<(BlockId, IdentifierId)>,
2049 + Terminal,
2050 + )> = env.functions[func_id.0 as usize]
2051 + .body
2052 + .blocks
2053 + .iter()
2054 + .map(|(bid, blk)| {
2055 + let phi_ops: Vec<(BlockId, IdentifierId)> = blk
2056 + .phis
2057 + .iter()
2058 + .flat_map(|phi| {
2059 + phi.operands
2060 + .iter()
2061 + .map(|(pred, place)| (*pred, place.identifier))
2062 + })
2063 + .collect();
2064 + (
2065 + *bid,
2066 + blk.instructions.clone(),
2067 + phi_ops,
2068 + blk.terminal.clone(),
2069 + )
2070 + })
2071 + .collect();
2072
2073 for (inner_bid, inner_instr_ids, inner_phis, inner_terminal) in &inner_blocks {
2074 for &(_pred_id, op_id) in inner_phis {
@@ -2101,9 +2139,7 @@ fn handle_instruction(
2139 ctx.visit_property(object, property, false, *loc, env);
2140 }
2141 InstructionValue::StoreLocal {
2104 - value: val,
2105 - lvalue,
2106 - ..
2142 + value: val, lvalue, ..
2143 } => {
2144 ctx.visit_operand(val, env);
2145 if lvalue.kind == InstructionKind::Reassign {
@@ -2134,9 +2170,7 @@ fn handle_instruction(
2170 }
2171 }
2172 InstructionValue::Destructure {
2137 - value: val,
2138 - lvalue,
2139 - ..
2173 + value: val, lvalue, ..
2174 } => {
2175 ctx.visit_operand(val, env);
2176 let pattern_places = visitors::each_pattern_operand(&lvalue.pattern);
@@ -2156,9 +2190,7 @@ fn handle_instruction(
2190 }
2191 }
2192 InstructionValue::StoreContext {
2159 - lvalue,
2160 - value: val,
2161 - ..
2193 + lvalue, value: val, ..
2194 } => {
2195 if !ctx.has_declared(lvalue.place.identifier, env)
2196 || lvalue.kind != InstructionKind::Reassign
@@ -2304,4 +2336,3 @@ fn handle_function_deps(
2336 }
2337 }
2338 }
2307 -
compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs
+5 -1
@@ -196,7 +196,11 @@ fn walk_lval_for_reassignment(
196 // Interior mutability - not a variable reassignment
197 }
198 PatternLike::TSAsExpression(node) => {
199 - record_unsupported_lval(visitor.env, "TSAsExpression", convert_opt_loc(&node.base.loc))?;
199 + record_unsupported_lval(
200 + visitor.env,
201 + "TSAsExpression",
202 + convert_opt_loc(&node.base.loc),
203 + )?;
204 }
205 PatternLike::TSSatisfiesExpression(node) => {
206 record_unsupported_lval(
compiler/crates/react_compiler_lowering/tests/unknown_statement_lowering.rs
+75 -75
@@ -1,8 +1,8 @@
1 use react_compiler_ast::scope::ScopeInfo;
2 use react_compiler_ast::statements::FunctionDeclaration;
3 -use react_compiler_hir::environment::Environment;
3 use react_compiler_hir::InstructionValue;
5 -use react_compiler_lowering::{lower, FunctionNode};
4 +use react_compiler_hir::environment::Environment;
5 +use react_compiler_lowering::{FunctionNode, lower};
6 use serde_json::json;
7
8 /// An unknown statement inside a function body degrades like the other
@@ -10,82 +10,82 @@ use serde_json::json;
10 /// UnsupportedNode instruction carries the raw node verbatim.
11 #[test]
12 fn unknown_statement_in_function_body_records_bailout() {
13 - let unknown_node = json!({
14 - "type": "TSFutureStatement",
15 - "start": 40,
16 - "end": 52,
17 - "payload": { "type": "Identifier", "name": "x" }
18 - });
19 - let func: FunctionDeclaration = serde_json::from_value(json!({
20 - "type": "FunctionDeclaration",
21 - "start": 0,
22 - "end": 60,
23 - "id": { "type": "Identifier", "name": "useValue", "start": 9, "end": 17 },
24 - "generator": false,
25 - "async": false,
26 - "params": [],
27 - "body": {
28 - "type": "BlockStatement",
29 - "start": 20,
30 - "end": 60,
31 - "body": [unknown_node.clone()],
32 - "directives": []
33 - }
34 - }))
35 - .unwrap();
13 + let unknown_node = json!({
14 + "type": "TSFutureStatement",
15 + "start": 40,
16 + "end": 52,
17 + "payload": { "type": "Identifier", "name": "x" }
18 + });
19 + let func: FunctionDeclaration = serde_json::from_value(json!({
20 + "type": "FunctionDeclaration",
21 + "start": 0,
22 + "end": 60,
23 + "id": { "type": "Identifier", "name": "useValue", "start": 9, "end": 17 },
24 + "generator": false,
25 + "async": false,
26 + "params": [],
27 + "body": {
28 + "type": "BlockStatement",
29 + "start": 20,
30 + "end": 60,
31 + "body": [unknown_node.clone()],
32 + "directives": []
33 + }
34 + }))
35 + .unwrap();
36
37 - let scope_info: ScopeInfo = serde_json::from_value(json!({
38 - "scopes": [
39 - { "id": 0, "parent": null, "kind": "program", "bindings": { "useValue": 0 } },
40 - { "id": 1, "parent": 0, "kind": "function", "bindings": {} }
41 - ],
42 - "bindings": [
43 - {
44 - "id": 0,
45 - "name": "useValue",
46 - "kind": "hoisted",
47 - "scope": 0,
48 - "declarationType": "FunctionDeclaration"
49 - }
50 - ],
51 - "nodeToScope": { "0": 1 },
52 - "referenceToBinding": {},
53 - "programScope": 0
54 - }))
55 - .unwrap();
37 + let scope_info: ScopeInfo = serde_json::from_value(json!({
38 + "scopes": [
39 + { "id": 0, "parent": null, "kind": "program", "bindings": { "useValue": 0 } },
40 + { "id": 1, "parent": 0, "kind": "function", "bindings": {} }
41 + ],
42 + "bindings": [
43 + {
44 + "id": 0,
45 + "name": "useValue",
46 + "kind": "hoisted",
47 + "scope": 0,
48 + "declarationType": "FunctionDeclaration"
49 + }
50 + ],
51 + "nodeToScope": { "0": 1 },
52 + "referenceToBinding": {},
53 + "programScope": 0
54 + }))
55 + .unwrap();
56
57 - let mut env = Environment::new();
58 - let result = lower(
59 - &FunctionNode::FunctionDeclaration(&func),
60 - None,
61 - &scope_info,
62 - &mut env,
63 - );
57 + let mut env = Environment::new();
58 + let result = lower(
59 + &FunctionNode::FunctionDeclaration(&func),
60 + None,
61 + &scope_info,
62 + &mut env,
63 + );
64
65 - assert!(
66 - env.has_errors(),
67 - "expected a recorded error, got result {result:?}"
68 - );
69 - let rendered = format!("{:?}", env.errors());
70 - assert!(
71 - rendered.contains("Unsupported statement kind 'TSFutureStatement'"),
72 - "unexpected error payload: {rendered}"
73 - );
65 + assert!(
66 + env.has_errors(),
67 + "expected a recorded error, got result {result:?}"
68 + );
69 + let rendered = format!("{:?}", env.errors());
70 + assert!(
71 + rendered.contains("Unsupported statement kind 'TSFutureStatement'"),
72 + "unexpected error payload: {rendered}"
73 + );
74
75 - let hir = result.expect("lowering degrades, it does not fail outright");
76 - let unsupported = hir
77 - .instructions
78 - .iter()
79 - .find_map(|instr| match &instr.value {
80 - InstructionValue::UnsupportedNode {
81 - node_type,
82 - original_node,
83 - ..
84 - } => Some((node_type.clone(), original_node.clone())),
85 - _ => None,
86 - })
87 - .expect("expected an UnsupportedNode instruction");
75 + let hir = result.expect("lowering degrades, it does not fail outright");
76 + let unsupported = hir
77 + .instructions
78 + .iter()
79 + .find_map(|instr| match &instr.value {
80 + InstructionValue::UnsupportedNode {
81 + node_type,
82 + original_node,
83 + ..
84 + } => Some((node_type.clone(), original_node.clone())),
85 + _ => None,
86 + })
87 + .expect("expected an UnsupportedNode instruction");
88
89 - assert_eq!(unsupported.0.as_deref(), Some("TSFutureStatement"));
90 - assert_eq!(unsupported.1, Some(unknown_node));
89 + assert_eq!(unsupported.0.as_deref(), Some("TSFutureStatement"));
90 + assert_eq!(unsupported.1, Some(unknown_node));
91 }
compiler/crates/react_compiler_optimization/src/constant_propagation.rs
+26 -32
@@ -182,7 +182,10 @@ fn apply_constant_propagation(
182 ..
183 } => {
184 let test_value = read(constants, test);
185 - if let Some(Constant::Primitive { value: ref prim, .. }) = test_value {
185 + if let Some(Constant::Primitive {
186 + value: ref prim, ..
187 + }) = test_value
188 + {
189 has_changes = true;
190 let target_block_id = if is_truthy(prim) {
191 *consequent
@@ -243,10 +246,7 @@ fn evaluate_phi(phi: &Phi, constants: &Constants) -> Option<Constant> {
246 continue;
247 }
248 Some(current) => match (current, operand_value) {
246 - (
247 - Constant::Primitive { value: a, .. },
248 - Constant::Primitive { value: b, .. },
249 - ) => {
249 + (Constant::Primitive { value: a, .. }, Constant::Primitive { value: b, .. }) => {
250 // Use JS strict equality semantics: NaN !== NaN
251 if !js_strict_equal(a, b) {
252 return None;
@@ -643,16 +643,12 @@ fn evaluate_instruction(
643 }
644 place_value
645 }
646 - InstructionValue::FunctionExpression {
647 - lowered_func, ..
648 - } => {
646 + InstructionValue::FunctionExpression { lowered_func, .. } => {
647 let func_id = lowered_func.func;
648 process_inner_function(func_id, env, constants);
649 None
650 }
653 - InstructionValue::ObjectMethod {
654 - lowered_func, ..
655 - } => {
651 + InstructionValue::ObjectMethod { lowered_func, .. } => {
652 let func_id = lowered_func.func;
653 process_inner_function(func_id, env, constants);
654 None
@@ -866,9 +862,9 @@ fn evaluate_binary_op(
862 ) -> Option<PrimitiveValue> {
863 match operator {
864 BinaryOperator::Add => match (lhs, rhs) {
869 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
870 - Some(PrimitiveValue::Number(FloatValue::new(l.value() + r.value())))
871 - }
865 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
866 + FloatValue::new(l.value() + r.value()),
867 + )),
868 (PrimitiveValue::String(l), PrimitiveValue::String(r)) => {
869 let mut s = l.clone();
870 s.push_str(r);
@@ -877,33 +873,33 @@ fn evaluate_binary_op(
873 _ => None,
874 },
875 BinaryOperator::Subtract => match (lhs, rhs) {
880 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
881 - Some(PrimitiveValue::Number(FloatValue::new(l.value() - r.value())))
882 - }
876 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
877 + FloatValue::new(l.value() - r.value()),
878 + )),
879 _ => None,
880 },
881 BinaryOperator::Multiply => match (lhs, rhs) {
886 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
887 - Some(PrimitiveValue::Number(FloatValue::new(l.value() * r.value())))
888 - }
882 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
883 + FloatValue::new(l.value() * r.value()),
884 + )),
885 _ => None,
886 },
887 BinaryOperator::Divide => match (lhs, rhs) {
892 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
893 - Some(PrimitiveValue::Number(FloatValue::new(l.value() / r.value())))
894 - }
888 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
889 + FloatValue::new(l.value() / r.value()),
890 + )),
891 _ => None,
892 },
893 BinaryOperator::Modulo => match (lhs, rhs) {
898 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => {
899 - Some(PrimitiveValue::Number(FloatValue::new(l.value() % r.value())))
900 - }
894 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
895 + FloatValue::new(l.value() % r.value()),
896 + )),
897 _ => None,
898 },
899 BinaryOperator::Exponent => match (lhs, rhs) {
904 - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(
905 - PrimitiveValue::Number(FloatValue::new(l.value().powf(r.value()))),
906 - ),
900 + (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number(
901 + FloatValue::new(l.value().powf(r.value())),
902 + )),
903 _ => None,
904 },
905 BinaryOperator::BitwiseOr => match (lhs, rhs) {
@@ -973,9 +969,7 @@ fn evaluate_binary_op(
969 _ => None,
970 },
971 BinaryOperator::StrictEqual => Some(PrimitiveValue::Boolean(js_strict_equal(lhs, rhs))),
976 - BinaryOperator::StrictNotEqual => {
977 - Some(PrimitiveValue::Boolean(!js_strict_equal(lhs, rhs)))
978 - }
972 + BinaryOperator::StrictNotEqual => Some(PrimitiveValue::Boolean(!js_strict_equal(lhs, rhs))),
973 BinaryOperator::Equal => Some(PrimitiveValue::Boolean(js_abstract_equal(lhs, rhs))),
974 BinaryOperator::NotEqual => Some(PrimitiveValue::Boolean(!js_abstract_equal(lhs, rhs))),
975 BinaryOperator::In | BinaryOperator::InstanceOf => None,
compiler/crates/react_compiler_optimization/src/dead_code_elimination.rs
+12 -19
@@ -17,8 +17,8 @@ use react_compiler_hir::environment::{Environment, OutputMode};
17 use react_compiler_hir::object_shape::HookKind;
18 use react_compiler_hir::visitors;
19 use react_compiler_hir::{
20 - ArrayPatternElement, BlockId, BlockKind, HirFunction, IdentifierId,
21 - InstructionKind, InstructionValue, ObjectPropertyOrSpread, Pattern,
20 + ArrayPatternElement, BlockId, BlockKind, HirFunction, IdentifierId, InstructionKind,
21 + InstructionValue, ObjectPropertyOrSpread, Pattern,
22 };
23
24 /// Implements dead-code elimination, eliminating instructions whose values are unused.
@@ -36,9 +36,9 @@ pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) {
36
37 for (_block_id, block) in &mut func.body.blocks {
38 // Remove unused phi nodes
39 - block.phis.retain(|phi| {
40 - is_id_or_name_used(&state, &env.identifiers, phi.place.identifier)
41 - });
39 + block
40 + .phis
41 + .retain(|phi| is_id_or_name_used(&state, &env.identifiers, phi.place.identifier));
42
43 // Remove instructions with unused lvalues
44 block.instructions.retain(|instr_id| {
@@ -49,8 +49,7 @@ pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) {
49 // Collect instructions that need rewriting (not the block value)
50 let retained_count = block.instructions.len();
51 for i in 0..retained_count {
52 - let is_block_value =
53 - block.kind != BlockKind::Block && i == retained_count - 1;
52 + let is_block_value = block.kind != BlockKind::Block && i == retained_count - 1;
53 if !is_block_value {
54 instructions_to_rewrite.push(block.instructions[i]);
55 }
@@ -63,9 +62,8 @@ pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) {
62 }
63
64 // Remove unused context variables
66 - func.context.retain(|ctx_var| {
67 - is_id_or_name_used(&state, &env.identifiers, ctx_var.identifier)
68 - });
65 + func.context
66 + .retain(|ctx_var| is_id_or_name_used(&state, &env.identifiers, ctx_var.identifier));
67 }
68
69 /// State for tracking referenced identifiers during mark phase.
@@ -151,8 +149,7 @@ fn find_referenced_identifiers(func: &HirFunction, env: &Environment) -> State {
149 let instr_id = block.instructions[i];
150 let instr = &func.instructions[instr_id.0 as usize];
151
154 - let is_block_value =
155 - block.kind != BlockKind::Block && i == instr_count - 1;
152 + let is_block_value = block.kind != BlockKind::Block && i == instr_count - 1;
153
154 if is_block_value {
155 // Last instr of a value block is never eligible for pruning
@@ -224,7 +221,8 @@ fn rewrite_instruction(
221 }
222 }
223 ArrayPatternElement::Spread(s) => {
227 - if !is_id_or_name_used(state, &env.identifiers, s.place.identifier) {
224 + if !is_id_or_name_used(state, &env.identifiers, s.place.identifier)
225 + {
226 arr.items[i] = ArrayPatternElement::Hole;
227 } else {
228 last_entry_index = i;
@@ -289,11 +287,7 @@ fn rewrite_instruction(
287 }
288
289 /// Returns true if it is safe to prune an instruction with the given value.
292 -fn pruneable_value(
293 - value: &InstructionValue,
294 - state: &State,
295 - env: &Environment,
296 -) -> bool {
290 +fn pruneable_value(value: &InstructionValue, state: &State, env: &Environment) -> bool {
291 match value {
292 InstructionValue::DeclareLocal { lvalue, .. } => {
293 // Declarations are pruneable only if the named variable is never read later
@@ -426,4 +420,3 @@ fn has_back_edge(func: &HirFunction) -> bool {
420 }
421 false
422 }
429 -
compiler/crates/react_compiler_optimization/src/inline_iifes.rs
-1
@@ -416,4 +416,3 @@ fn promote_temporary(env: &mut Environment, identifier_id: IdentifierId) {
416 env.identifiers[identifier_id.0 as usize].name =
417 Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
418 }
419 -
compiler/crates/react_compiler_optimization/src/merge_consecutive_blocks.rs
+1 -4
@@ -47,10 +47,7 @@ pub fn merge_consecutive_blocks(func: &mut HirFunction, functions: &mut [HirFunc
47 for func_id in inner_func_ids {
48 // Use std::mem::replace to temporarily take the inner function out,
49 // process it, then put it back (standard borrow checker workaround)
50 - let mut inner_func = std::mem::replace(
51 - &mut functions[func_id],
52 - placeholder_function(),
53 - );
50 + let mut inner_func = std::mem::replace(&mut functions[func_id], placeholder_function());
51 merge_consecutive_blocks(&mut inner_func, functions);
52 functions[func_id] = inner_func;
53 }
compiler/crates/react_compiler_optimization/src/name_anonymous_functions.rs
+29 -25
@@ -16,8 +16,8 @@ use std::collections::HashMap;
16 use react_compiler_hir::environment::Environment;
17 use react_compiler_hir::object_shape::HookKind;
18 use react_compiler_hir::{
19 - FunctionId, HirFunction, IdentifierId, IdentifierName, InstructionValue, JsxAttribute, JsxTag,
20 - PlaceOrSpread, Instruction,
19 + FunctionId, HirFunction, IdentifierId, IdentifierName, Instruction, InstructionValue,
20 + JsxAttribute, JsxTag, PlaceOrSpread,
21 };
22
23 /// Assign generated names to anonymous function expressions.
@@ -31,11 +31,7 @@ pub fn name_anonymous_functions(func: &mut HirFunction, env: &mut Environment) {
31
32 let nodes = name_anonymous_functions_impl(func, env);
33
34 - fn visit(
35 - node: &Node,
36 - prefix: &str,
37 - updates: &mut Vec<(FunctionId, String)>,
38 - ) {
34 + fn visit(node: &Node, prefix: &str, updates: &mut Vec<(FunctionId, String)>) {
35 if node.generated_name.is_some() && node.existing_name_hint.is_none() {
36 // Only add the prefix to anonymous functions regardless of nesting depth
37 let name = format!("{}{}]", prefix, node.generated_name.as_ref().unwrap());
@@ -150,16 +146,11 @@ fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<N
146 object, property, ..
147 } => {
148 if let Some(object_name) = names.get(&object.identifier) {
153 - names.insert(
154 - lvalue_id,
155 - format!("{}.{}", object_name, property),
156 - );
149 + names.insert(lvalue_id, format!("{}.{}", object_name, property));
150 }
151 }
152 InstructionValue::FunctionExpression {
160 - name,
161 - lowered_func,
162 - ..
153 + name, lowered_func, ..
154 } => {
155 let inner_func = &env.functions[lowered_func.func.0 as usize];
156 let inner = name_anonymous_functions_impl(inner_func, env);
@@ -177,8 +168,16 @@ fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<N
168 functions.insert(lvalue_id, idx);
169 }
170 }
180 - InstructionValue::StoreContext { lvalue: store_lvalue, value, .. }
181 - | InstructionValue::StoreLocal { lvalue: store_lvalue, value, .. } => {
171 + InstructionValue::StoreContext {
172 + lvalue: store_lvalue,
173 + value,
174 + ..
175 + }
176 + | InstructionValue::StoreLocal {
177 + lvalue: store_lvalue,
178 + value,
179 + ..
180 + } => {
181 if let Some(&node_idx) = functions.get(&value.identifier) {
182 let node = &mut nodes[node_idx];
183 let var_ident = &env.identifiers[store_lvalue.place.identifier.0 as usize];
@@ -201,9 +200,7 @@ fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<N
200 &mut nodes,
201 );
202 }
204 - InstructionValue::MethodCall {
205 - property, args, ..
206 - } => {
203 + InstructionValue::MethodCall { property, args, .. } => {
204 handle_call(
205 env,
206 func,
@@ -218,14 +215,15 @@ fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<N
215 for attr in props {
216 match attr {
217 JsxAttribute::SpreadAttribute { .. } => continue,
221 - JsxAttribute::Attribute { name: attr_name, place } => {
218 + JsxAttribute::Attribute {
219 + name: attr_name,
220 + place,
221 + } => {
222 if let Some(&node_idx) = functions.get(&place.identifier) {
223 let node = &mut nodes[node_idx];
224 if node.generated_name.is_none() {
225 let element_name = match tag {
226 - JsxTag::Builtin(builtin) => {
227 - Some(builtin.name.clone())
228 - }
226 + JsxTag::Builtin(builtin) => Some(builtin.name.clone()),
227 JsxTag::Place(tag_place) => {
228 names.get(&tag_place.identifier).cloned()
229 }
@@ -270,10 +268,16 @@ fn handle_call(
268 if *hk != HookKind::Custom {
269 hk.to_string()
270 } else {
273 - names.get(&callee_id).cloned().unwrap_or_else(|| "(anonymous)".to_string())
271 + names
272 + .get(&callee_id)
273 + .cloned()
274 + .unwrap_or_else(|| "(anonymous)".to_string())
275 }
276 } else {
276 - names.get(&callee_id).cloned().unwrap_or_else(|| "(anonymous)".to_string())
277 + names
278 + .get(&callee_id)
279 + .cloned()
280 + .unwrap_or_else(|| "(anonymous)".to_string())
281 };
282
283 // Count how many args are tracked functions
compiler/crates/react_compiler_optimization/src/optimize_for_ssr.rs
+27 -34
@@ -50,7 +50,8 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
50 let instr = &func.instructions[instr_id.0 as usize];
51 match &instr.value {
52 InstructionValue::Destructure { value, lvalue, .. } => {
53 - if inlined_state.contains_key(&env.identifiers[value.identifier.0 as usize].id) {
53 + if inlined_state.contains_key(&env.identifiers[value.identifier.0 as usize].id)
54 + {
55 if let react_compiler_hir::Pattern::Array(arr) = &lvalue.pattern {
56 if !arr.items.is_empty() {
57 if let ArrayPatternElement::Place(_) = &arr.items[0] {
@@ -61,9 +62,7 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
62 }
63 }
64 }
64 - InstructionValue::MethodCall {
65 - property, args, ..
66 - }
65 + InstructionValue::MethodCall { property, args, .. }
66 | InstructionValue::CallExpression {
67 callee: property,
68 args,
@@ -75,14 +74,11 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
74 match hook_kind {
75 Some(HookKind::UseReducer) => {
76 if args.len() == 2 {
78 - if let (
79 - PlaceOrSpread::Place(_),
80 - PlaceOrSpread::Place(arg),
81 - ) = (&args[0], &args[1])
77 + if let (PlaceOrSpread::Place(_), PlaceOrSpread::Place(arg)) =
78 + (&args[0], &args[1])
79 {
83 - let lvalue_id = env.identifiers
84 - [instr.lvalue.identifier.0 as usize]
85 - .id;
80 + let lvalue_id =
81 + env.identifiers[instr.lvalue.identifier.0 as usize].id;
82 inlined_state.insert(
83 lvalue_id,
84 InlinedStateReplacement::LoadLocal {
@@ -98,9 +94,8 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
94 PlaceOrSpread::Place(initializer),
95 ) = (&args[0], &args[1], &args[2])
96 {
101 - let lvalue_id = env.identifiers
102 - [instr.lvalue.identifier.0 as usize]
103 - .id;
97 + let lvalue_id =
98 + env.identifiers[instr.lvalue.identifier.0 as usize].id;
99 let call_loc = instr.value.loc().copied();
100 inlined_state.insert(
101 lvalue_id,
@@ -116,16 +111,17 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
111 Some(HookKind::UseState) => {
112 if args.len() == 1 {
113 if let PlaceOrSpread::Place(arg) = &args[0] {
119 - let arg_type = &env.types
120 - [env.identifiers[arg.identifier.0 as usize].type_.0
121 - as usize];
114 + let arg_type = &env.types[env.identifiers
115 + [arg.identifier.0 as usize]
116 + .type_
117 + .0
118 + as usize];
119 if react_compiler_hir::is_primitive_type(arg_type)
120 || react_compiler_hir::is_plain_object_type(arg_type)
121 || react_compiler_hir::is_array_type(arg_type)
122 {
126 - let lvalue_id = env.identifiers
127 - [instr.lvalue.identifier.0 as usize]
128 - .id;
123 + let lvalue_id =
124 + env.identifiers[instr.lvalue.identifier.0 as usize].id;
125 inlined_state.insert(
126 lvalue_id,
127 InlinedStateReplacement::LoadLocal {
@@ -145,8 +141,7 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
141
142 // Any use of useState/useReducer return besides destructuring prevents inlining
143 if !inlined_state.is_empty() {
148 - let operands =
149 - each_instruction_value_operand(&instr.value, env);
144 + let operands = each_instruction_value_operand(&instr.value, env);
145 for operand in &operands {
146 let id = env.identifiers[operand.identifier.0 as usize].id;
147 inlined_state.remove(&id);
@@ -192,19 +187,15 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
187 if !builtin.name.contains('-') {
188 let tag_name = builtin.name.clone();
189 // Retain only props that are not known event handlers and not "ref"
195 - if let InstructionValue::JsxExpression { props, .. } =
196 - &mut instr.value
190 + if let InstructionValue::JsxExpression { props, .. } = &mut instr.value
191 {
192 props.retain(|prop| match prop {
199 - react_compiler_hir::JsxAttribute::SpreadAttribute { .. } => {
200 - true
201 - }
193 + react_compiler_hir::JsxAttribute::SpreadAttribute {
194 + ..
195 + } => true,
196 react_compiler_hir::JsxAttribute::Attribute {
197 name, ..
204 - } => {
205 - !is_known_event_handler(&tag_name, name)
206 - && name != "ref"
207 - }
198 + } => !is_known_event_handler(&tag_name, name) && name != "ref",
199 });
200 }
201 }
@@ -235,7 +226,10 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
226 }
227 }
228 InstructionValue::MethodCall {
238 - property, args, loc, ..
229 + property,
230 + args,
231 + loc,
232 + ..
233 }
234 | InstructionValue::CallExpression {
235 callee: property,
@@ -269,8 +263,7 @@ pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) {
263 };
264 }
265 Some(HookKind::UseReducer | HookKind::UseState) => {
272 - let lvalue_id =
273 - env.identifiers[instr.lvalue.identifier.0 as usize].id;
266 + let lvalue_id = env.identifiers[instr.lvalue.identifier.0 as usize].id;
267 if let Some(replacement) = inlined_state.get(&lvalue_id) {
268 instr.value = match replacement {
269 InlinedStateReplacement::LoadLocal { place, loc } => {
compiler/crates/react_compiler_optimization/src/optimize_props_method_calls.rs
+3 -5
@@ -14,7 +14,7 @@
14 //! Analogous to TS `Optimization/OptimizePropsMethodCalls.ts`.
15
16 use react_compiler_hir::environment::Environment;
17 -use react_compiler_hir::{is_props_type, HirFunction, InstructionValue};
17 +use react_compiler_hir::{HirFunction, InstructionValue, is_props_type};
18
19 pub fn optimize_props_method_calls(func: &mut HirFunction, env: &Environment) {
20 for (_block_id, block) in &func.body.blocks {
@@ -33,10 +33,8 @@ pub fn optimize_props_method_calls(func: &mut HirFunction, env: &Environment) {
33 if should_replace {
34 // Take the old value out, replacing with a temporary.
35 // The if-let is guaranteed to match since we checked above.
36 - let old = std::mem::replace(
37 - &mut instr.value,
38 - InstructionValue::Debugger { loc: None },
39 - );
36 + let old =
37 + std::mem::replace(&mut instr.value, InstructionValue::Debugger { loc: None });
38 match old {
39 InstructionValue::MethodCall {
40 property,
compiler/crates/react_compiler_optimization/src/outline_functions.rs
+2 -5
@@ -47,9 +47,7 @@ pub fn outline_functions(
47 let lvalue_id = instr.lvalue.identifier;
48
49 match &instr.value {
50 - InstructionValue::FunctionExpression {
51 - lowered_func, ..
52 - } => {
50 + InstructionValue::FunctionExpression { lowered_func, .. } => {
51 let inner_func = &env.functions[lowered_func.func.0 as usize];
52
53 // Check outlining conditions (TS only checks func.id === null, not name):
@@ -107,8 +105,7 @@ pub fn outline_functions(
105 .id
106 .clone()
107 .or_else(|| env.functions[function_id.0 as usize].name_hint.clone());
110 - let generated_name =
111 - env.generate_globally_unique_identifier_name(hint.as_deref());
108 + let generated_name = env.generate_globally_unique_identifier_name(hint.as_deref());
109
110 // Set the id on the inner function
111 env.functions[function_id.0 as usize].id = Some(generated_name.clone());
compiler/crates/react_compiler_optimization/src/outline_jsx.rs
+37 -15
@@ -13,11 +13,11 @@ use std::collections::{HashMap, HashSet};
13 use indexmap::IndexMap;
14 use react_compiler_hir::environment::Environment;
15 use react_compiler_hir::{
16 - BasicBlock, BlockId, BlockKind, EvaluationOrder, HirFunction, HIR, IdentifierId, Instruction,
17 - InstructionId, InstructionKind, InstructionValue, JsxAttribute, JsxTag,
18 - NonLocalBinding, ObjectProperty, ObjectPropertyKey, ObjectPropertyOrSpread,
19 - ObjectPropertyType, ObjectPattern, ParamPattern, Pattern, Place, ReactFunctionType,
20 - Terminal, ReturnVariant, IdentifierName, LValuePattern, FunctionId,
16 + BasicBlock, BlockId, BlockKind, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId,
17 + IdentifierName, Instruction, InstructionId, InstructionKind, InstructionValue, JsxAttribute,
18 + JsxTag, LValuePattern, NonLocalBinding, ObjectPattern, ObjectProperty, ObjectPropertyKey,
19 + ObjectPropertyOrSpread, ObjectPropertyType, ParamPattern, Pattern, Place, ReactFunctionType,
20 + ReturnVariant, Terminal,
21 };
22
23 /// Outline JSX expressions in inner functions into separate outlined components.
@@ -34,9 +34,9 @@ pub fn outline_jsx(func: &mut HirFunction, env: &mut Environment) {
34
35 /// Data about a JSX instruction for outlining
36 struct JsxInstrInfo {
37 - instr_idx: usize, // index into func.instructions
37 + instr_idx: usize, // index into func.instructions
38 #[allow(dead_code)]
39 - instr_id: InstructionId, // the InstructionId
39 + instr_id: InstructionId, // the InstructionId
40 lvalue_id: IdentifierId,
41 eval_order: EvaluationOrder,
42 }
@@ -72,8 +72,13 @@ fn outline_jsx_impl(
72
73 // First pass: collect all instruction info without borrowing func mutably
74 enum InstrAction {
75 - LoadGlobal { lvalue_id: IdentifierId, instr_idx: usize },
76 - FunctionExpr { func_id: FunctionId },
75 + LoadGlobal {
76 + lvalue_id: IdentifierId,
77 + instr_idx: usize,
78 + },
79 + FunctionExpr {
80 + func_id: FunctionId,
81 + },
82 JsxExpr {
83 lvalue_id: IdentifierId,
84 instr_idx: usize,
@@ -91,13 +96,19 @@ fn outline_jsx_impl(
96
97 match &instr.value {
98 InstructionValue::LoadGlobal { .. } => {
94 - actions.push(InstrAction::LoadGlobal { lvalue_id, instr_idx: iid.0 as usize });
99 + actions.push(InstrAction::LoadGlobal {
100 + lvalue_id,
101 + instr_idx: iid.0 as usize,
102 + });
103 }
104 InstructionValue::FunctionExpression { lowered_func, .. } => {
97 - actions.push(InstrAction::FunctionExpr { func_id: lowered_func.func });
105 + actions.push(InstrAction::FunctionExpr {
106 + func_id: lowered_func.func,
107 + });
108 }
109 InstructionValue::JsxExpression { children, .. } => {
100 - let child_ids = children.as_ref()
110 + let child_ids = children
111 + .as_ref()
112 .map(|kids| kids.iter().map(|c| c.identifier).collect())
113 .unwrap_or_default();
114 actions.push(InstrAction::JsxExpr {
@@ -116,7 +127,10 @@ fn outline_jsx_impl(
127 // Second pass: process actions
128 for action in actions {
129 match action {
119 - InstrAction::LoadGlobal { lvalue_id, instr_idx } => {
130 + InstrAction::LoadGlobal {
131 + lvalue_id,
132 + instr_idx,
133 + } => {
134 globals.insert(lvalue_id, instr_idx);
135 }
136 InstrAction::FunctionExpr { func_id } => {
@@ -127,7 +141,12 @@ fn outline_jsx_impl(
141 outline_jsx_impl(&mut inner_func, env, outlined_fns);
142 env.functions[func_id.0 as usize] = inner_func;
143 }
130 - InstrAction::JsxExpr { lvalue_id, instr_idx, eval_order, child_ids } => {
144 + InstrAction::JsxExpr {
145 + lvalue_id,
146 + instr_idx,
147 + eval_order,
148 + child_ids,
149 + } => {
150 if !children_ids.contains(&lvalue_id) {
151 process_and_outline_jsx(
152 func,
@@ -265,7 +284,10 @@ fn collect_props(
284
285 for info in jsx_group {
286 let instr = &func.instructions[info.instr_idx];
268 - if let InstructionValue::JsxExpression { props, children, .. } = &instr.value {
287 + if let InstructionValue::JsxExpression {
288 + props, children, ..
289 + } = &instr.value
290 + {
291 for attr in props {
292 match attr {
293 JsxAttribute::SpreadAttribute { .. } => return None,
compiler/crates/react_compiler_optimization/src/prune_maybe_throws.rs
+1 -4
@@ -15,9 +15,7 @@ use std::collections::HashMap;
15 use react_compiler_diagnostics::{
16 CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, GENERATED_SOURCE,
17 };
18 -use react_compiler_hir::{
19 - BlockId, HirFunction, Instruction, InstructionValue, Terminal,
20 -};
18 +use react_compiler_hir::{BlockId, HirFunction, Instruction, InstructionValue, Terminal};
19 use react_compiler_lowering::{
20 get_reverse_postordered_blocks, mark_instruction_ids, remove_dead_do_while_statements,
21 remove_unnecessary_try_catch, remove_unreachable_for_updates,
@@ -84,7 +82,6 @@ pub fn prune_maybe_throws(
82 }
83 }
84 }
87 -
85 }
86 Ok(())
87 }
compiler/crates/react_compiler_optimization/src/prune_unused_labels_hir.rs
+4 -1
@@ -48,7 +48,10 @@ pub fn prune_unused_labels_hir(func: &mut HirFunction) {
48 let mut rewrites: HashMap<BlockId, BlockId> = HashMap::new();
49
50 for (original_label_id, next_id, fallthrough_id) in &merged {
51 - let label_id = rewrites.get(original_label_id).copied().unwrap_or(*original_label_id);
51 + let label_id = rewrites
52 + .get(original_label_id)
53 + .copied()
54 + .unwrap_or(*original_label_id);
55
56 // Validate: no phis in next or fallthrough
57 let next_phis_empty = func.body.blocks[next_id].phis.is_empty();
compiler/crates/react_compiler_oxc/Cargo.toml deleted
-22
@@ -1,22 +0,0 @@
1 -[package]
2 -name = "react_compiler_oxc"
3 -version = "0.1.0"
4 -edition = "2024"
5 -
6 -[dependencies]
7 -react_compiler_ast = { path = "../react_compiler_ast" }
8 -react_compiler = { path = "../react_compiler" }
9 -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" }
10 -react_compiler_hir = { path = "../react_compiler_hir" }
11 -oxc_parser = "0.121"
12 -oxc_ast = "0.121"
13 -oxc_ast_visit = "0.121"
14 -oxc_semantic = "0.121"
15 -oxc_allocator = "0.121"
16 -oxc_span = "0.121"
17 -oxc_diagnostics = "0.121"
18 -oxc_syntax = "0.121"
19 -oxc_codegen = "0.121"
20 -indexmap = { version = "2", features = ["serde"] }
21 -serde = { version = "1", features = ["derive"] }
22 -serde_json = "1"
compiler/crates/react_compiler_oxc/src/apply_renames.rs deleted
-195
@@ -1,195 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -//! Apply compiler binding renames to an OXC AST.
7 -//!
8 -//! When the React Compiler renames a binding (e.g., `x` → `x_0` to avoid
9 -//! shadowing conflicts), compiled functions already contain the renamed
10 -//! identifiers. However, uncompiled sibling functions that reference the same
11 -//! binding still use the original name. This module walks the OXC program
12 -//! and renames identifiers at positions recorded in the rewrite plan.
13 -
14 -use std::collections::HashMap;
15 -
16 -use oxc_allocator::FromIn;
17 -use oxc_ast::ast::*;
18 -use oxc_ast_visit::walk_mut;
19 -use oxc_ast_visit::VisitMut;
20 -use oxc_span::Atom;
21 -use react_compiler::entrypoint::compile_result::BindingRenameInfo;
22 -use react_compiler_ast::scope::BindingId;
23 -use react_compiler_ast::scope::ScopeInfo;
24 -
25 -/// Build a rewrite plan from the scope info and compiler renames.
26 -///
27 -/// Returns a map from source position to the new identifier name.
28 -/// This uses the scope info's `ref_node_id_to_binding` to find all references
29 -/// to renamed bindings. In OXC, node_id == span.start, so the positions
30 -/// in the rewrite plan correspond to `span.start` values in the OXC AST.
31 -pub fn build_rename_plan(
32 - scope_info: &ScopeInfo,
33 - renames: &[BindingRenameInfo],
34 -) -> HashMap<u32, String> {
35 - if renames.is_empty() {
36 - return HashMap::new();
37 - }
38 -
39 - // Map declaration_start -> rename info
40 - let renames_by_declaration: HashMap<u32, &BindingRenameInfo> = renames
41 - .iter()
42 - .map(|rename| (rename.declaration_start, rename))
43 - .collect();
44 -
45 - // Find which BindingIds have been renamed
46 - let mut renamed_bindings: HashMap<BindingId, String> = HashMap::new();
47 - for binding in &scope_info.bindings {
48 - let Some(rename) = binding
49 - .declaration_start
50 - .and_then(|start| renames_by_declaration.get(&start))
51 - else {
52 - continue;
53 - };
54 - if binding.name == rename.original {
55 - renamed_bindings.insert(binding.id, rename.renamed.clone());
56 - }
57 - }
58 -
59 - if renamed_bindings.is_empty() {
60 - return HashMap::new();
61 - }
62 -
63 - // Build position -> new_name map from all references to renamed bindings.
64 - // Uses ref_node_id_to_binding (node-ID keyed; in OXC node_id == span.start).
65 - scope_info
66 - .ref_node_id_to_binding
67 - .iter()
68 - .filter_map(|(&position, binding_id)| {
69 - renamed_bindings
70 - .get(binding_id)
71 - .map(|renamed| (position, renamed.clone()))
72 - })
73 - .collect()
74 -}
75 -
76 -/// Apply renames to an OXC program using a pre-computed rewrite plan.
77 -///
78 -/// The rewrite plan maps span.start positions to new identifier names.
79 -/// This function walks the AST and renames:
80 -/// - `IdentifierReference` nodes (variable reads)
81 -/// - `BindingIdentifier` nodes (variable declarations/params)
82 -///
83 -/// Special handling is applied for:
84 -/// - Shorthand object properties `{x}` → `{x: x_0}`
85 -/// - Shorthand binding properties `{x}` → `{x: x_0}` in destructuring patterns
86 -pub fn apply_renames<'a>(
87 - program: &mut Program<'a>,
88 - rename_plan: &HashMap<u32, String>,
89 - allocator: &'a oxc_allocator::Allocator,
90 -) {
91 - if rename_plan.is_empty() {
92 - return;
93 - }
94 - walk_mut::walk_program(
95 - &mut RenameApplyVisitor {
96 - rename_plan,
97 - allocator,
98 - },
99 - program,
100 - );
101 -}
102 -
103 -struct RenameApplyVisitor<'a, 'p> {
104 - rename_plan: &'p HashMap<u32, String>,
105 - allocator: &'a oxc_allocator::Allocator,
106 -}
107 -
108 -impl<'a, 'p> RenameApplyVisitor<'a, 'p> {
109 - fn renamed_at(&self, position: u32) -> Option<&str> {
110 - self.rename_plan.get(&position).map(|s| s.as_str())
111 - }
112 -}
113 -
114 -impl<'a> VisitMut<'a> for RenameApplyVisitor<'a, '_> {
115 - /// Rename identifier references (variable reads/uses).
116 - fn visit_identifier_reference(&mut self, ident: &mut IdentifierReference<'a>) {
117 - if let Some(renamed) = self.renamed_at(ident.span.start) {
118 - ident.name = Atom::from_in(renamed, self.allocator).into();
119 - }
120 - }
121 -
122 - /// Rename binding identifiers (variable declarations/params).
123 - fn visit_binding_identifier(&mut self, ident: &mut BindingIdentifier<'a>) {
124 - if let Some(renamed) = self.renamed_at(ident.span.start) {
125 - ident.name = Atom::from_in(renamed, self.allocator).into();
126 - }
127 - }
128 -
129 - /// Handle member expressions: only rename the object, not static property names.
130 - /// `obj.prop` — rename `obj` if needed, never rename `prop`.
131 - /// `obj[expr]` — rename `obj` and recurse into `expr`.
132 - fn visit_member_expression(&mut self, expr: &mut MemberExpression<'a>) {
133 - match expr {
134 - MemberExpression::StaticMemberExpression(static_expr) => {
135 - // Only visit the object, skip the property (it's a fixed name)
136 - self.visit_expression(&mut static_expr.object);
137 - }
138 - MemberExpression::ComputedMemberExpression(computed_expr) => {
139 - self.visit_expression(&mut computed_expr.object);
140 - self.visit_expression(&mut computed_expr.expression);
141 - }
142 - MemberExpression::PrivateFieldExpression(private_expr) => {
143 - self.visit_expression(&mut private_expr.object);
144 - // Private field names are never renamed
145 - }
146 - }
147 - }
148 -
149 - /// Handle object properties: convert shorthand `{x}` to `{x: x_0}` when renamed.
150 - fn visit_object_property(&mut self, prop: &mut ObjectProperty<'a>) {
151 - if prop.shorthand {
152 - // Shorthand property like `{x}` — check if the value identifier is renamed
153 - if let Expression::Identifier(ref ident) = prop.value {
154 - if let Some(renamed) = self.renamed_at(ident.span.start) {
155 - // Convert shorthand to key-value: `{x}` → `{x: x_0}`
156 - prop.shorthand = false;
157 - // The key stays as the original name (already set)
158 - // The value gets the renamed identifier
159 - if let Expression::Identifier(ref mut ident) = prop.value {
160 - ident.name = Atom::from_in(renamed, self.allocator).into();
161 - }
162 - return;
163 - }
164 - }
165 - }
166 - // For non-shorthand properties, only visit the value (and computed keys)
167 - if prop.computed {
168 - self.visit_property_key(&mut prop.key);
169 - }
170 - self.visit_expression(&mut prop.value);
171 - }
172 -
173 - /// Handle binding properties in destructuring: `{x}` → `{x: x_0}` when renamed.
174 - fn visit_binding_property(&mut self, prop: &mut BindingProperty<'a>) {
175 - if prop.shorthand {
176 - // Shorthand binding like `const {x} = obj` — check if binding is renamed
177 - if let BindingPattern::BindingIdentifier(ref ident) = prop.value {
178 - if let Some(renamed) = self.renamed_at(ident.span.start) {
179 - // Convert shorthand to key-value: `{x}` → `{x: x_0}`
180 - prop.shorthand = false;
181 - // Rename the binding identifier
182 - if let BindingPattern::BindingIdentifier(ref mut ident) = prop.value {
183 - ident.name = Atom::from_in(renamed, self.allocator).into();
184 - }
185 - return;
186 - }
187 - }
188 - }
189 - // For non-shorthand, visit the value pattern (and computed keys)
190 - if prop.computed {
191 - self.visit_property_key(&mut prop.key);
192 - }
193 - self.visit_binding_pattern(&mut prop.value);
194 - }
195 -}
compiler/crates/react_compiler_oxc/src/convert_ast.rs deleted
-2896
@@ -1,2896 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -use oxc_ast::ast as oxc;
8 -use oxc_span::Span;
9 -use react_compiler_ast::File;
10 -use react_compiler_ast::Program;
11 -use react_compiler_ast::SourceType;
12 -use react_compiler_ast::common::BaseNode;
13 -use react_compiler_ast::common::RawNode;
14 -use react_compiler_ast::common::Comment;
15 -use react_compiler_ast::common::CommentData;
16 -use react_compiler_ast::common::Position;
17 -use react_compiler_ast::common::SourceLocation;
18 -use react_compiler_ast::declarations::*;
19 -use react_compiler_ast::expressions::*;
20 -use react_compiler_ast::jsx::*;
21 -use react_compiler_ast::literals::*;
22 -use react_compiler_ast::operators::*;
23 -use react_compiler_ast::patterns::*;
24 -use react_compiler_ast::statements::*;
25 -
26 -/// Converts an OXC AST to the React compiler's Babel-compatible AST.
27 -pub fn convert_program(program: &oxc::Program, source_text: &str) -> File {
28 - let ctx = ConvertCtx::new(source_text);
29 - let base = ctx.make_base_node(program.span);
30 -
31 - let mut body = Vec::new();
32 - for stmt in &program.body {
33 - body.push(ctx.convert_statement(stmt));
34 - }
35 -
36 - let directives = program
37 - .directives
38 - .iter()
39 - .map(|d| ctx.convert_directive(d))
40 - .collect();
41 -
42 - let source_type = match program.source_type.is_module() {
43 - true => SourceType::Module,
44 - false => SourceType::Script,
45 - };
46 -
47 - // Convert OXC comments
48 - let comments = ctx.convert_comments(&program.comments);
49 -
50 - File {
51 - base: ctx.make_base_node(program.span),
52 - program: Program {
53 - base,
54 - body,
55 - directives,
56 - source_type,
57 - interpreter: None,
58 - source_file: None,
59 - },
60 - comments,
61 - errors: vec![],
62 - }
63 -}
64 -
65 -struct ConvertCtx<'a> {
66 - source_text: &'a str,
67 - line_offsets: Vec<u32>,
68 -}
69 -
70 -impl<'a> ConvertCtx<'a> {
71 - fn new(source_text: &'a str) -> Self {
72 - let mut line_offsets = vec![0];
73 - for (i, ch) in source_text.char_indices() {
74 - if ch == '\n' {
75 - line_offsets.push((i + 1) as u32);
76 - }
77 - }
78 - Self {
79 - source_text,
80 - line_offsets,
81 - }
82 - }
83 -
84 - fn make_base_node(&self, span: Span) -> BaseNode {
85 - BaseNode {
86 - node_type: None,
87 - start: Some(span.start),
88 - end: Some(span.end),
89 - loc: Some(self.source_location(span)),
90 - range: None,
91 - extra: None,
92 - node_id: Some(span.start),
93 - leading_comments: None,
94 - inner_comments: None,
95 - trailing_comments: None,
96 - }
97 - }
98 -
99 - fn position(&self, offset: u32) -> Position {
100 - let line_idx = match self.line_offsets.binary_search(&offset) {
101 - Ok(idx) => idx,
102 - Err(idx) => idx.saturating_sub(1),
103 - };
104 - let line_start = self.line_offsets[line_idx];
105 - Position {
106 - line: (line_idx + 1) as u32,
107 - column: offset - line_start,
108 - index: Some(offset),
109 - }
110 - }
111 -
112 - fn source_location(&self, span: Span) -> SourceLocation {
113 - SourceLocation {
114 - start: self.position(span.start),
115 - end: self.position(span.end),
116 - filename: None,
117 - identifier_name: None,
118 - }
119 - }
120 -
121 - fn convert_comments(&self, comments: &[oxc::Comment]) -> Vec<Comment> {
122 - comments
123 - .iter()
124 - .map(|comment| {
125 - let base = self.make_base_node(comment.span);
126 - // OXC comment spans include delimiters (// or /* */), so we need
127 - // to strip them to get the content-only value that the compiler expects.
128 - let raw = &self.source_text[comment.span.start as usize..comment.span.end as usize];
129 - let value = match comment.kind {
130 - oxc::CommentKind::Line => {
131 - // Strip leading //
132 - raw.strip_prefix("//").unwrap_or(raw).trim().to_string()
133 - }
134 - oxc::CommentKind::SingleLineBlock | oxc::CommentKind::MultiLineBlock => {
135 - // Strip leading /* and trailing */
136 - let stripped = raw
137 - .strip_prefix("/*")
138 - .unwrap_or(raw)
139 - .strip_suffix("*/")
140 - .unwrap_or(raw);
141 - stripped.trim().to_string()
142 - }
143 - };
144 - let comment_data = CommentData {
145 - value,
146 - start: base.start,
147 - end: base.end,
148 - loc: base.loc.clone(),
149 - };
150 - match comment.kind {
151 - oxc::CommentKind::Line => Comment::CommentLine(comment_data),
152 - oxc::CommentKind::SingleLineBlock | oxc::CommentKind::MultiLineBlock => {
153 - Comment::CommentBlock(comment_data)
154 - }
155 - }
156 - })
157 - .collect()
158 - }
159 -
160 - fn convert_directive(&self, directive: &oxc::Directive) -> Directive {
161 - let base = self.make_base_node(directive.span);
162 - Directive {
163 - base,
164 - value: DirectiveLiteral {
165 - base: self.make_base_node(directive.expression.span),
166 - value: directive.expression.value.to_string(),
167 - },
168 - }
169 - }
170 -
171 - fn convert_statement(&self, stmt: &oxc::Statement) -> Statement {
172 - match stmt {
173 - oxc::Statement::BlockStatement(s) => {
174 - Statement::BlockStatement(self.convert_block_statement(s))
175 - }
176 - oxc::Statement::ReturnStatement(s) => {
177 - Statement::ReturnStatement(self.convert_return_statement(s))
178 - }
179 - oxc::Statement::IfStatement(s) => Statement::IfStatement(self.convert_if_statement(s)),
180 - oxc::Statement::ForStatement(s) => {
181 - Statement::ForStatement(self.convert_for_statement(s))
182 - }
183 - oxc::Statement::WhileStatement(s) => {
184 - Statement::WhileStatement(self.convert_while_statement(s))
185 - }
186 - oxc::Statement::DoWhileStatement(s) => {
187 - Statement::DoWhileStatement(self.convert_do_while_statement(s))
188 - }
189 - oxc::Statement::ForInStatement(s) => {
190 - Statement::ForInStatement(self.convert_for_in_statement(s))
191 - }
192 - oxc::Statement::ForOfStatement(s) => {
193 - Statement::ForOfStatement(self.convert_for_of_statement(s))
194 - }
195 - oxc::Statement::SwitchStatement(s) => {
196 - Statement::SwitchStatement(self.convert_switch_statement(s))
197 - }
198 - oxc::Statement::ThrowStatement(s) => {
199 - Statement::ThrowStatement(self.convert_throw_statement(s))
200 - }
201 - oxc::Statement::TryStatement(s) => {
202 - Statement::TryStatement(self.convert_try_statement(s))
203 - }
204 - oxc::Statement::BreakStatement(s) => {
205 - Statement::BreakStatement(self.convert_break_statement(s))
206 - }
207 - oxc::Statement::ContinueStatement(s) => {
208 - Statement::ContinueStatement(self.convert_continue_statement(s))
209 - }
210 - oxc::Statement::LabeledStatement(s) => {
211 - Statement::LabeledStatement(self.convert_labeled_statement(s))
212 - }
213 - oxc::Statement::ExpressionStatement(s) => {
214 - Statement::ExpressionStatement(self.convert_expression_statement(s))
215 - }
216 - oxc::Statement::EmptyStatement(s) => Statement::EmptyStatement(EmptyStatement {
217 - base: self.make_base_node(s.span),
218 - }),
219 - oxc::Statement::DebuggerStatement(s) => {
220 - Statement::DebuggerStatement(DebuggerStatement {
221 - base: self.make_base_node(s.span),
222 - })
223 - }
224 - oxc::Statement::WithStatement(s) => {
225 - Statement::WithStatement(self.convert_with_statement(s))
226 - }
227 - // Declaration variants (inherited)
228 - oxc::Statement::VariableDeclaration(v) => {
229 - Statement::VariableDeclaration(self.convert_variable_declaration(v))
230 - }
231 - oxc::Statement::FunctionDeclaration(f) => {
232 - Statement::FunctionDeclaration(self.convert_function_declaration(f))
233 - }
234 - oxc::Statement::ClassDeclaration(c) => {
235 - Statement::ClassDeclaration(self.convert_class_declaration(c))
236 - }
237 - oxc::Statement::TSTypeAliasDeclaration(t) => {
238 - Statement::TSTypeAliasDeclaration(self.convert_ts_type_alias_declaration(t))
239 - }
240 - oxc::Statement::TSInterfaceDeclaration(t) => {
241 - Statement::TSInterfaceDeclaration(self.convert_ts_interface_declaration(t))
242 - }
243 - oxc::Statement::TSEnumDeclaration(t) => {
244 - Statement::TSEnumDeclaration(self.convert_ts_enum_declaration(t))
245 - }
246 - oxc::Statement::TSModuleDeclaration(t) => {
247 - Statement::TSModuleDeclaration(self.convert_ts_module_declaration(t))
248 - }
249 - oxc::Statement::TSImportEqualsDeclaration(_) => {
250 - // Pass through as opaque JSON for now
251 - todo!("TSImportEqualsDeclaration")
252 - }
253 - // ModuleDeclaration variants (inherited directly into Statement)
254 - oxc::Statement::ImportDeclaration(i) => {
255 - Statement::ImportDeclaration(self.convert_import_declaration(i))
256 - }
257 - oxc::Statement::ExportAllDeclaration(e) => {
258 - Statement::ExportAllDeclaration(self.convert_export_all_declaration(e))
259 - }
260 - oxc::Statement::ExportDefaultDeclaration(e) => {
261 - Statement::ExportDefaultDeclaration(self.convert_export_default_declaration(e))
262 - }
263 - oxc::Statement::ExportNamedDeclaration(e) => {
264 - Statement::ExportNamedDeclaration(self.convert_export_named_declaration(e))
265 - }
266 - oxc::Statement::TSExportAssignment(_) => {
267 - todo!("TSExportAssignment")
268 - }
269 - oxc::Statement::TSNamespaceExportDeclaration(_) => {
270 - todo!("TSNamespaceExportDeclaration")
271 - }
272 - oxc::Statement::TSGlobalDeclaration(_) => {
273 - todo!("TSGlobalDeclaration")
274 - }
275 - }
276 - }
277 -
278 - fn convert_block_statement(&self, block: &oxc::BlockStatement) -> BlockStatement {
279 - let base = self.make_base_node(block.span);
280 - let body = block
281 - .body
282 - .iter()
283 - .map(|s| self.convert_statement(s))
284 - .collect();
285 - BlockStatement {
286 - base,
287 - body,
288 - directives: vec![],
289 - }
290 - }
291 -
292 - fn convert_return_statement(&self, ret: &oxc::ReturnStatement) -> ReturnStatement {
293 - ReturnStatement {
294 - base: self.make_base_node(ret.span),
295 - argument: ret
296 - .argument
297 - .as_ref()
298 - .map(|e| Box::new(self.convert_expression(e))),
299 - }
300 - }
301 -
302 - fn convert_if_statement(&self, if_stmt: &oxc::IfStatement) -> IfStatement {
303 - IfStatement {
304 - base: self.make_base_node(if_stmt.span),
305 - test: Box::new(self.convert_expression(&if_stmt.test)),
306 - consequent: Box::new(self.convert_statement(&if_stmt.consequent)),
307 - alternate: if_stmt
308 - .alternate
309 - .as_ref()
310 - .map(|a| Box::new(self.convert_statement(a))),
311 - }
312 - }
313 -
314 - fn convert_for_statement(&self, for_stmt: &oxc::ForStatement) -> ForStatement {
315 - ForStatement {
316 - base: self.make_base_node(for_stmt.span),
317 - init: for_stmt.init.as_ref().map(|init| {
318 - Box::new(match init {
319 - oxc::ForStatementInit::VariableDeclaration(v) => {
320 - ForInit::VariableDeclaration(self.convert_variable_declaration(v))
321 - }
322 - _ => ForInit::Expression(Box::new(self.convert_expression_like(init))),
323 - })
324 - }),
325 - test: for_stmt
326 - .test
327 - .as_ref()
328 - .map(|t| Box::new(self.convert_expression(t))),
329 - update: for_stmt
330 - .update
331 - .as_ref()
332 - .map(|u| Box::new(self.convert_expression(u))),
333 - body: Box::new(self.convert_statement(&for_stmt.body)),
334 - }
335 - }
336 -
337 - fn convert_while_statement(&self, while_stmt: &oxc::WhileStatement) -> WhileStatement {
338 - WhileStatement {
339 - base: self.make_base_node(while_stmt.span),
340 - test: Box::new(self.convert_expression(&while_stmt.test)),
341 - body: Box::new(self.convert_statement(&while_stmt.body)),
342 - }
343 - }
344 -
345 - fn convert_do_while_statement(&self, do_while: &oxc::DoWhileStatement) -> DoWhileStatement {
346 - DoWhileStatement {
347 - base: self.make_base_node(do_while.span),
348 - test: Box::new(self.convert_expression(&do_while.test)),
349 - body: Box::new(self.convert_statement(&do_while.body)),
350 - }
351 - }
352 -
353 - fn convert_for_in_statement(&self, for_in: &oxc::ForInStatement) -> ForInStatement {
354 - ForInStatement {
355 - base: self.make_base_node(for_in.span),
356 - left: Box::new(self.convert_for_in_of_left(&for_in.left)),
357 - right: Box::new(self.convert_expression(&for_in.right)),
358 - body: Box::new(self.convert_statement(&for_in.body)),
359 - }
360 - }
361 -
362 - fn convert_for_of_statement(&self, for_of: &oxc::ForOfStatement) -> ForOfStatement {
363 - ForOfStatement {
364 - base: self.make_base_node(for_of.span),
365 - left: Box::new(self.convert_for_in_of_left(&for_of.left)),
366 - right: Box::new(self.convert_expression(&for_of.right)),
367 - body: Box::new(self.convert_statement(&for_of.body)),
368 - is_await: for_of.r#await,
369 - }
370 - }
371 -
372 - fn convert_for_in_of_left(&self, left: &oxc::ForStatementLeft) -> ForInOfLeft {
373 - match left {
374 - oxc::ForStatementLeft::VariableDeclaration(v) => {
375 - ForInOfLeft::VariableDeclaration(self.convert_variable_declaration(v))
376 - }
377 - oxc::ForStatementLeft::AssignmentTargetIdentifier(i) => {
378 - ForInOfLeft::Pattern(Box::new(PatternLike::Identifier(Identifier {
379 - base: self.make_base_node(i.span),
380 - name: i.name.to_string(),
381 - type_annotation: None,
382 - optional: None,
383 - decorators: None,
384 - })))
385 - }
386 - oxc::ForStatementLeft::ArrayAssignmentTarget(a) => {
387 - ForInOfLeft::Pattern(Box::new(self.convert_array_assignment_target(a)))
388 - }
389 - oxc::ForStatementLeft::ObjectAssignmentTarget(o) => {
390 - ForInOfLeft::Pattern(Box::new(self.convert_object_assignment_target(o)))
391 - }
392 - oxc::ForStatementLeft::ComputedMemberExpression(m) => {
393 - let mem = MemberExpression {
394 - base: self.make_base_node(m.span),
395 - object: Box::new(self.convert_expression(&m.object)),
396 - property: Box::new(self.convert_expression(&m.expression)),
397 - computed: true,
398 - };
399 - ForInOfLeft::Pattern(Box::new(PatternLike::MemberExpression(mem)))
400 - }
401 - oxc::ForStatementLeft::StaticMemberExpression(m) => {
402 - let mem = MemberExpression {
403 - base: self.make_base_node(m.span),
404 - object: Box::new(self.convert_expression(&m.object)),
405 - property: Box::new(Expression::Identifier(
406 - self.convert_identifier_name(&m.property),
407 - )),
408 - computed: false,
409 - };
410 - ForInOfLeft::Pattern(Box::new(PatternLike::MemberExpression(mem)))
411 - }
412 - oxc::ForStatementLeft::PrivateFieldExpression(p) => {
413 - let mem = MemberExpression {
414 - base: self.make_base_node(p.span),
415 - object: Box::new(self.convert_expression(&p.object)),
416 - property: Box::new(Expression::PrivateName(PrivateName {
417 - base: self.make_base_node(p.field.span),
418 - id: Identifier {
419 - base: self.make_base_node(p.field.span),
420 - name: p.field.name.to_string(),
421 - type_annotation: None,
422 - optional: None,
423 - decorators: None,
424 - },
425 - })),
426 - computed: false,
427 - };
428 - ForInOfLeft::Pattern(Box::new(PatternLike::MemberExpression(mem)))
429 - }
430 - oxc::ForStatementLeft::TSAsExpression(_)
431 - | oxc::ForStatementLeft::TSSatisfiesExpression(_)
432 - | oxc::ForStatementLeft::TSNonNullExpression(_)
433 - | oxc::ForStatementLeft::TSTypeAssertion(_) => {
434 - todo!("TypeScript expression in for-in/of left")
435 - }
436 - }
437 - }
438 -
439 - fn convert_switch_statement(&self, switch: &oxc::SwitchStatement) -> SwitchStatement {
440 - SwitchStatement {
441 - base: self.make_base_node(switch.span),
442 - discriminant: Box::new(self.convert_expression(&switch.discriminant)),
443 - cases: switch
444 - .cases
445 - .iter()
446 - .map(|c| self.convert_switch_case(c))
447 - .collect(),
448 - }
449 - }
450 -
451 - fn convert_switch_case(&self, case: &oxc::SwitchCase) -> SwitchCase {
452 - SwitchCase {
453 - base: self.make_base_node(case.span),
454 - test: case
455 - .test
456 - .as_ref()
457 - .map(|t| Box::new(self.convert_expression(t))),
458 - consequent: case
459 - .consequent
460 - .iter()
461 - .map(|s| self.convert_statement(s))
462 - .collect(),
463 - }
464 - }
465 -
466 - fn convert_throw_statement(&self, throw: &oxc::ThrowStatement) -> ThrowStatement {
467 - ThrowStatement {
468 - base: self.make_base_node(throw.span),
469 - argument: Box::new(self.convert_expression(&throw.argument)),
470 - }
471 - }
472 -
473 - fn convert_try_statement(&self, try_stmt: &oxc::TryStatement) -> TryStatement {
474 - TryStatement {
475 - base: self.make_base_node(try_stmt.span),
476 - block: self.convert_block_statement(&try_stmt.block),
477 - handler: try_stmt
478 - .handler
479 - .as_ref()
480 - .map(|h| self.convert_catch_clause(h)),
481 - finalizer: try_stmt
482 - .finalizer
483 - .as_ref()
484 - .map(|f| self.convert_block_statement(f)),
485 - }
486 - }
487 -
488 - fn convert_catch_clause(&self, catch: &oxc::CatchClause) -> CatchClause {
489 - CatchClause {
490 - base: self.make_base_node(catch.span),
491 - param: catch
492 - .param
493 - .as_ref()
494 - .map(|p| self.convert_binding_pattern(&p.pattern)),
495 - body: self.convert_block_statement(&catch.body),
496 - }
497 - }
498 -
499 - fn convert_break_statement(&self, brk: &oxc::BreakStatement) -> BreakStatement {
500 - BreakStatement {
501 - base: self.make_base_node(brk.span),
502 - label: brk.label.as_ref().map(|l| self.convert_label_identifier(l)),
503 - }
504 - }
505 -
506 - fn convert_continue_statement(&self, cont: &oxc::ContinueStatement) -> ContinueStatement {
507 - ContinueStatement {
508 - base: self.make_base_node(cont.span),
509 - label: cont
510 - .label
511 - .as_ref()
512 - .map(|l| self.convert_label_identifier(l)),
513 - }
514 - }
515 -
516 - fn convert_labeled_statement(&self, labeled: &oxc::LabeledStatement) -> LabeledStatement {
517 - LabeledStatement {
518 - base: self.make_base_node(labeled.span),
519 - label: self.convert_label_identifier(&labeled.label),
520 - body: Box::new(self.convert_statement(&labeled.body)),
521 - }
522 - }
523 -
524 - fn convert_expression_statement(
525 - &self,
526 - expr_stmt: &oxc::ExpressionStatement,
527 - ) -> ExpressionStatement {
528 - ExpressionStatement {
529 - base: self.make_base_node(expr_stmt.span),
530 - expression: Box::new(self.convert_expression(&expr_stmt.expression)),
531 - }
532 - }
533 -
534 - fn convert_with_statement(&self, with: &oxc::WithStatement) -> WithStatement {
535 - WithStatement {
536 - base: self.make_base_node(with.span),
537 - object: Box::new(self.convert_expression(&with.object)),
538 - body: Box::new(self.convert_statement(&with.body)),
539 - }
540 - }
541 -
542 - fn convert_variable_declaration(&self, var: &oxc::VariableDeclaration) -> VariableDeclaration {
543 - VariableDeclaration {
544 - base: self.make_base_node(var.span),
545 - declarations: var
546 - .declarations
547 - .iter()
548 - .map(|d| self.convert_variable_declarator(d))
549 - .collect(),
550 - kind: match var.kind {
551 - oxc::VariableDeclarationKind::Var => VariableDeclarationKind::Var,
552 - oxc::VariableDeclarationKind::Let => VariableDeclarationKind::Let,
553 - oxc::VariableDeclarationKind::Const => VariableDeclarationKind::Const,
554 - oxc::VariableDeclarationKind::Using => VariableDeclarationKind::Using,
555 - oxc::VariableDeclarationKind::AwaitUsing => {
556 - // Map to Using for now
557 - VariableDeclarationKind::Using
558 - }
559 - },
560 - declare: if var.declare { Some(true) } else { None },
561 - }
562 - }
563 -
564 - fn convert_variable_declarator(
565 - &self,
566 - declarator: &oxc::VariableDeclarator,
567 - ) -> VariableDeclarator {
568 - VariableDeclarator {
569 - base: self.make_base_node(declarator.span),
570 - id: self.convert_binding_pattern(&declarator.id),
571 - init: declarator
572 - .init
573 - .as_ref()
574 - .map(|i| Box::new(self.convert_expression(i))),
575 - definite: if declarator.definite {
576 - Some(true)
577 - } else {
578 - None
579 - },
580 - }
581 - }
582 -
583 - fn convert_function_declaration(&self, func: &oxc::Function) -> FunctionDeclaration {
584 - FunctionDeclaration {
585 - base: self.make_base_node(func.span),
586 - id: func
587 - .id
588 - .as_ref()
589 - .map(|id| self.convert_binding_identifier(id)),
590 - params: self.convert_formal_parameters(&func.params),
591 - body: self.convert_function_body(func.body.as_ref().unwrap()),
592 - generator: func.generator,
593 - is_async: func.r#async,
594 - declare: if func.declare { Some(true) } else { None },
595 - return_type: func
596 - .return_type
597 - .as_ref()
598 - .map(|_t| RawNode::null()),
599 - type_parameters: func
600 - .type_parameters
601 - .as_ref()
602 - .map(|_t| RawNode::null()),
603 - predicate: None,
604 - component_declaration: false,
605 - hook_declaration: false,
606 - }
607 - }
608 -
609 - fn convert_class_declaration(&self, class: &oxc::Class) -> ClassDeclaration {
610 - ClassDeclaration {
611 - base: self.make_base_node(class.span),
612 - id: class
613 - .id
614 - .as_ref()
615 - .map(|id| self.convert_binding_identifier(id)),
616 - super_class: class
617 - .super_class
618 - .as_ref()
619 - .map(|s| Box::new(self.convert_expression(s))),
620 - body: ClassBody {
621 - base: self.make_base_node(class.body.span),
622 - body: class
623 - .body
624 - .body
625 - .iter()
626 - .map(|_item| RawNode::null())
627 - .collect(),
628 - },
629 - decorators: if class.decorators.is_empty() {
630 - None
631 - } else {
632 - Some(
633 - class
634 - .decorators
635 - .iter()
636 - .map(|_d| RawNode::null())
637 - .collect(),
638 - )
639 - },
640 - is_abstract: if class.r#abstract { Some(true) } else { None },
641 - declare: if class.declare { Some(true) } else { None },
642 - implements: if class.implements.is_empty() {
643 - None
644 - } else {
645 - Some(
646 - class
647 - .implements
648 - .iter()
649 - .map(|_i| RawNode::null())
650 - .collect(),
651 - )
652 - },
653 - super_type_parameters: class
654 - .super_type_arguments
655 - .as_ref()
656 - .map(|_t| RawNode::null()),
657 - type_parameters: class
658 - .type_parameters
659 - .as_ref()
660 - .map(|_t| RawNode::null()),
661 - mixins: None,
662 - }
663 - }
664 -
665 - fn convert_import_declaration(&self, import: &oxc::ImportDeclaration) -> ImportDeclaration {
666 - ImportDeclaration {
667 - base: self.make_base_node(import.span),
668 - specifiers: import
669 - .specifiers
670 - .as_ref()
671 - .map(|specs| {
672 - specs
673 - .iter()
674 - .flat_map(|s| self.convert_import_declaration_specifier(s))
675 - .collect()
676 - })
677 - .unwrap_or_default(),
678 - source: StringLiteral {
679 - base: self.make_base_node(import.source.span),
680 - value: import.source.value.to_string(),
681 - },
682 - import_kind: match import.import_kind {
683 - oxc::ImportOrExportKind::Value => None,
684 - oxc::ImportOrExportKind::Type => Some(ImportKind::Type),
685 - },
686 - assertions: None,
687 - attributes: if import.with_clause.is_some() {
688 - Some(
689 - import
690 - .with_clause
691 - .as_ref()
692 - .unwrap()
693 - .with_entries
694 - .iter()
695 - .map(|e| self.convert_import_attribute(e))
696 - .collect(),
697 - )
698 - } else {
699 - None
700 - },
701 - }
702 - }
703 -
704 - fn convert_import_declaration_specifier(
705 - &self,
706 - spec: &oxc::ImportDeclarationSpecifier,
707 - ) -> Option<ImportSpecifier> {
708 - match spec {
709 - oxc::ImportDeclarationSpecifier::ImportSpecifier(s) => {
710 - Some(ImportSpecifier::ImportSpecifier(ImportSpecifierData {
711 - base: self.make_base_node(s.span),
712 - local: self.convert_binding_identifier(&s.local),
713 - imported: self.convert_module_export_name(&s.imported),
714 - import_kind: match s.import_kind {
715 - oxc::ImportOrExportKind::Value => None,
716 - oxc::ImportOrExportKind::Type => Some(ImportKind::Type),
717 - },
718 - }))
719 - }
720 - oxc::ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => Some(
721 - ImportSpecifier::ImportDefaultSpecifier(ImportDefaultSpecifierData {
722 - base: self.make_base_node(s.span),
723 - local: self.convert_binding_identifier(&s.local),
724 - }),
725 - ),
726 - oxc::ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => Some(
727 - ImportSpecifier::ImportNamespaceSpecifier(ImportNamespaceSpecifierData {
728 - base: self.make_base_node(s.span),
729 - local: self.convert_binding_identifier(&s.local),
730 - }),
731 - ),
732 - }
733 - }
734 -
735 - fn convert_import_attribute(&self, attr: &oxc::ImportAttribute) -> ImportAttribute {
736 - ImportAttribute {
737 - base: self.make_base_node(attr.span),
738 - key: self.convert_import_attribute_key(&attr.key),
739 - value: StringLiteral {
740 - base: self.make_base_node(attr.value.span),
741 - value: attr.value.value.to_string(),
742 - },
743 - }
744 - }
745 -
746 - fn convert_import_attribute_key(&self, key: &oxc::ImportAttributeKey) -> Identifier {
747 - match key {
748 - oxc::ImportAttributeKey::Identifier(id) => Identifier {
749 - base: self.make_base_node(id.span),
750 - name: id.name.to_string(),
751 - type_annotation: None,
752 - optional: None,
753 - decorators: None,
754 - },
755 - oxc::ImportAttributeKey::StringLiteral(s) => Identifier {
756 - base: self.make_base_node(s.span),
757 - name: s.value.to_string(),
758 - type_annotation: None,
759 - optional: None,
760 - decorators: None,
761 - },
762 - }
763 - }
764 -
765 - fn convert_module_export_name(&self, name: &oxc::ModuleExportName) -> ModuleExportName {
766 - match name {
767 - oxc::ModuleExportName::IdentifierName(id) => {
768 - ModuleExportName::Identifier(self.convert_identifier_name(id))
769 - }
770 - oxc::ModuleExportName::IdentifierReference(id) => {
771 - ModuleExportName::Identifier(self.convert_identifier_reference(id))
772 - }
773 - oxc::ModuleExportName::StringLiteral(s) => {
774 - ModuleExportName::StringLiteral(StringLiteral {
775 - base: self.make_base_node(s.span),
776 - value: s.value.to_string(),
777 - })
778 - }
779 - }
780 - }
781 -
782 - fn convert_export_all_declaration(
783 - &self,
784 - export: &oxc::ExportAllDeclaration,
785 - ) -> ExportAllDeclaration {
786 - ExportAllDeclaration {
787 - base: self.make_base_node(export.span),
788 - source: StringLiteral {
789 - base: self.make_base_node(export.source.span),
790 - value: export.source.value.to_string(),
791 - },
792 - export_kind: match export.export_kind {
793 - oxc::ImportOrExportKind::Value => None,
794 - oxc::ImportOrExportKind::Type => Some(ExportKind::Type),
795 - },
796 - assertions: None,
797 - attributes: if export.with_clause.is_some() {
798 - Some(
799 - export
800 - .with_clause
801 - .as_ref()
802 - .unwrap()
803 - .with_entries
804 - .iter()
805 - .map(|e| self.convert_import_attribute(e))
806 - .collect(),
807 - )
808 - } else {
809 - None
810 - },
811 - }
812 - }
813 -
814 - fn convert_export_default_declaration(
815 - &self,
816 - export: &oxc::ExportDefaultDeclaration,
817 - ) -> ExportDefaultDeclaration {
818 - let declaration = match &export.declaration {
819 - oxc::ExportDefaultDeclarationKind::FunctionDeclaration(f) => {
820 - ExportDefaultDecl::FunctionDeclaration(self.convert_function_declaration(f))
821 - }
822 - oxc::ExportDefaultDeclarationKind::ClassDeclaration(c) => {
823 - ExportDefaultDecl::ClassDeclaration(self.convert_class_declaration(c))
824 - }
825 - oxc::ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => {
826 - todo!("TSInterfaceDeclaration in export default")
827 - }
828 - _ => {
829 - // All expression variants
830 - ExportDefaultDecl::Expression(Box::new(
831 - self.convert_export_default_expr(&export.declaration),
832 - ))
833 - }
834 - };
835 -
836 - ExportDefaultDeclaration {
837 - base: self.make_base_node(export.span),
838 - declaration: Box::new(declaration),
839 - export_kind: None,
840 - }
841 - }
842 -
843 - fn convert_export_default_expr(&self, kind: &oxc::ExportDefaultDeclarationKind) -> Expression {
844 - match kind {
845 - oxc::ExportDefaultDeclarationKind::FunctionDeclaration(_)
846 - | oxc::ExportDefaultDeclarationKind::ClassDeclaration(_)
847 - | oxc::ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => {
848 - panic!("Should be handled separately")
849 - }
850 - other => self.convert_expression_from_export_default(other),
851 - }
852 - }
853 -
854 - fn convert_export_named_declaration(
855 - &self,
856 - export: &oxc::ExportNamedDeclaration,
857 - ) -> ExportNamedDeclaration {
858 - ExportNamedDeclaration {
859 - base: self.make_base_node(export.span),
860 - declaration: export.declaration.as_ref().map(|d| {
861 - Box::new(match d {
862 - oxc::Declaration::VariableDeclaration(v) => {
863 - Declaration::VariableDeclaration(self.convert_variable_declaration(v))
864 - }
865 - oxc::Declaration::FunctionDeclaration(f) => {
866 - Declaration::FunctionDeclaration(self.convert_function_declaration(f))
867 - }
868 - oxc::Declaration::ClassDeclaration(c) => {
869 - Declaration::ClassDeclaration(self.convert_class_declaration(c))
870 - }
871 - oxc::Declaration::TSTypeAliasDeclaration(t) => {
872 - Declaration::TSTypeAliasDeclaration(
873 - self.convert_ts_type_alias_declaration(t),
874 - )
875 - }
876 - oxc::Declaration::TSInterfaceDeclaration(t) => {
877 - Declaration::TSInterfaceDeclaration(
878 - self.convert_ts_interface_declaration(t),
879 - )
880 - }
881 - oxc::Declaration::TSEnumDeclaration(t) => {
882 - Declaration::TSEnumDeclaration(self.convert_ts_enum_declaration(t))
883 - }
884 - oxc::Declaration::TSModuleDeclaration(t) => {
885 - Declaration::TSModuleDeclaration(self.convert_ts_module_declaration(t))
886 - }
887 - oxc::Declaration::TSImportEqualsDeclaration(_) => {
888 - todo!("TSImportEqualsDeclaration")
889 - }
890 - oxc::Declaration::TSGlobalDeclaration(_) => {
891 - todo!("TSGlobalDeclaration")
892 - }
893 - })
894 - }),
895 - specifiers: export
896 - .specifiers
897 - .iter()
898 - .map(|s| self.convert_export_specifier(s))
899 - .collect(),
900 - source: export.source.as_ref().map(|s| StringLiteral {
901 - base: self.make_base_node(s.span),
902 - value: s.value.to_string(),
903 - }),
904 - export_kind: match export.export_kind {
905 - oxc::ImportOrExportKind::Value => None,
906 - oxc::ImportOrExportKind::Type => Some(ExportKind::Type),
907 - },
908 - assertions: None,
909 - attributes: if export.with_clause.is_some() {
910 - Some(
911 - export
912 - .with_clause
913 - .as_ref()
914 - .unwrap()
915 - .with_entries
916 - .iter()
917 - .map(|e| self.convert_import_attribute(e))
918 - .collect(),
919 - )
920 - } else {
921 - None
922 - },
923 - }
924 - }
925 -
926 - fn convert_export_specifier(&self, spec: &oxc::ExportSpecifier) -> ExportSpecifier {
927 - // ExportSpecifier is now a struct in OXC v0.121, not an enum
928 - ExportSpecifier::ExportSpecifier(ExportSpecifierData {
929 - base: self.make_base_node(spec.span),
930 - local: self.convert_module_export_name(&spec.local),
931 - exported: self.convert_module_export_name(&spec.exported),
932 - export_kind: match spec.export_kind {
933 - oxc::ImportOrExportKind::Value => None,
934 - oxc::ImportOrExportKind::Type => Some(ExportKind::Type),
935 - },
936 - })
937 - }
938 -
939 - fn convert_ts_type_alias_declaration(
940 - &self,
941 - type_alias: &oxc::TSTypeAliasDeclaration,
942 - ) -> TSTypeAliasDeclaration {
943 - TSTypeAliasDeclaration {
944 - base: self.make_base_node(type_alias.span),
945 - id: self.convert_binding_identifier(&type_alias.id),
946 - type_annotation: RawNode::null(),
947 - type_parameters: type_alias
948 - .type_parameters
949 - .as_ref()
950 - .map(|_t| RawNode::null()),
951 - declare: if type_alias.declare { Some(true) } else { None },
952 - }
953 - }
954 -
955 - fn convert_ts_interface_declaration(
956 - &self,
957 - interface: &oxc::TSInterfaceDeclaration,
958 - ) -> TSInterfaceDeclaration {
959 - TSInterfaceDeclaration {
960 - base: self.make_base_node(interface.span),
961 - id: self.convert_binding_identifier(&interface.id),
962 - body: RawNode::null(),
963 - type_parameters: interface
964 - .type_parameters
965 - .as_ref()
966 - .map(|_t| RawNode::null()),
967 - extends: if interface.extends.is_empty() {
968 - None
969 - } else {
970 - Some(
971 - interface
972 - .extends
973 - .iter()
974 - .map(|_e| RawNode::null())
975 - .collect(),
976 - )
977 - },
978 - declare: if interface.declare { Some(true) } else { None },
979 - }
980 - }
981 -
982 - fn convert_ts_enum_declaration(&self, ts_enum: &oxc::TSEnumDeclaration) -> TSEnumDeclaration {
983 - TSEnumDeclaration {
984 - base: self.make_base_node(ts_enum.span),
985 - id: self.convert_binding_identifier(&ts_enum.id),
986 - members: ts_enum
987 - .body
988 - .members
989 - .iter()
990 - .map(|_m| RawNode::null())
991 - .collect(),
992 - declare: if ts_enum.declare { Some(true) } else { None },
993 - is_const: if ts_enum.r#const { Some(true) } else { None },
994 - }
995 - }
996 -
997 - fn convert_ts_module_declaration(
998 - &self,
999 - module: &oxc::TSModuleDeclaration,
1000 - ) -> TSModuleDeclaration {
1001 - TSModuleDeclaration {
1002 - base: self.make_base_node(module.span),
1003 - id: RawNode::null(),
1004 - body: RawNode::null(),
1005 - declare: if module.declare { Some(true) } else { None },
1006 - global: None,
1007 - }
1008 - }
1009 -
1010 - fn convert_expression(&self, expr: &oxc::Expression) -> Expression {
1011 - match expr {
1012 - oxc::Expression::BooleanLiteral(b) => Expression::BooleanLiteral(BooleanLiteral {
1013 - base: self.make_base_node(b.span),
1014 - value: b.value,
1015 - }),
1016 - oxc::Expression::NullLiteral(n) => Expression::NullLiteral(NullLiteral {
1017 - base: self.make_base_node(n.span),
1018 - }),
1019 - oxc::Expression::NumericLiteral(n) => Expression::NumericLiteral(NumericLiteral {
1020 - base: self.make_base_node(n.span),
1021 - value: n.value,
1022 - extra: None,
1023 - }),
1024 - oxc::Expression::BigIntLiteral(b) => Expression::BigIntLiteral(BigIntLiteral {
1025 - base: self.make_base_node(b.span),
1026 - value: b.raw.as_ref().map(|r| r.to_string()).unwrap_or_default(),
1027 - }),
1028 - oxc::Expression::RegExpLiteral(r) => Expression::RegExpLiteral(RegExpLiteral {
1029 - base: self.make_base_node(r.span),
1030 - pattern: r.regex.pattern.text.to_string(),
1031 - flags: r.regex.flags.to_string(),
1032 - }),
1033 - oxc::Expression::StringLiteral(s) => Expression::StringLiteral(StringLiteral {
1034 - base: self.make_base_node(s.span),
1035 - value: s.value.to_string(),
1036 - }),
1037 - oxc::Expression::TemplateLiteral(t) => {
1038 - Expression::TemplateLiteral(self.convert_template_literal(t))
1039 - }
1040 - oxc::Expression::Identifier(id) => {
1041 - Expression::Identifier(self.convert_identifier_reference(id))
1042 - }
1043 - oxc::Expression::MetaProperty(m) => {
1044 - Expression::MetaProperty(self.convert_meta_property(m))
1045 - }
1046 - oxc::Expression::Super(s) => Expression::Super(Super {
1047 - base: self.make_base_node(s.span),
1048 - }),
1049 - oxc::Expression::ArrayExpression(a) => {
1050 - Expression::ArrayExpression(self.convert_array_expression(a))
1051 - }
1052 - oxc::Expression::ArrowFunctionExpression(a) => {
1053 - Expression::ArrowFunctionExpression(self.convert_arrow_function_expression(a))
1054 - }
1055 - oxc::Expression::AssignmentExpression(a) => {
1056 - Expression::AssignmentExpression(self.convert_assignment_expression(a))
1057 - }
1058 - oxc::Expression::AwaitExpression(a) => {
1059 - Expression::AwaitExpression(self.convert_await_expression(a))
1060 - }
1061 - oxc::Expression::BinaryExpression(b) => {
1062 - Expression::BinaryExpression(self.convert_binary_expression(b))
1063 - }
1064 - oxc::Expression::CallExpression(c) => {
1065 - Expression::CallExpression(self.convert_call_expression(c))
1066 - }
1067 - oxc::Expression::ChainExpression(c) => self.convert_chain_expression(c),
1068 - oxc::Expression::ClassExpression(c) => {
1069 - Expression::ClassExpression(self.convert_class_expression(c))
1070 - }
1071 - oxc::Expression::ConditionalExpression(c) => {
1072 - Expression::ConditionalExpression(self.convert_conditional_expression(c))
1073 - }
1074 - oxc::Expression::FunctionExpression(f) => {
1075 - Expression::FunctionExpression(self.convert_function_expression(f))
1076 - }
1077 - oxc::Expression::ImportExpression(i) => {
1078 - // OXC models `import('foo')` as ImportExpression { source, options }.
1079 - // Babel/compiler AST models it as CallExpression { callee: Import, arguments: [source] }.
1080 - let mut args = vec![self.convert_expression(&i.source)];
1081 - if let Some(ref opts) = i.options {
1082 - args.push(self.convert_expression(opts));
1083 - }
1084 - Expression::CallExpression(CallExpression {
1085 - base: self.make_base_node(i.span),
1086 - callee: Box::new(Expression::Import(Import {
1087 - base: self.make_base_node(i.span),
1088 - })),
1089 - arguments: args,
1090 - optional: None,
1091 - type_arguments: None,
1092 - type_parameters: None,
1093 - })
1094 - }
1095 - oxc::Expression::LogicalExpression(l) => {
1096 - Expression::LogicalExpression(self.convert_logical_expression(l))
1097 - }
1098 - oxc::Expression::NewExpression(n) => {
1099 - Expression::NewExpression(self.convert_new_expression(n))
1100 - }
1101 - oxc::Expression::ObjectExpression(o) => {
1102 - Expression::ObjectExpression(self.convert_object_expression(o))
1103 - }
1104 - oxc::Expression::ParenthesizedExpression(p) => {
1105 - Expression::ParenthesizedExpression(self.convert_parenthesized_expression(p))
1106 - }
1107 - oxc::Expression::SequenceExpression(s) => {
1108 - Expression::SequenceExpression(self.convert_sequence_expression(s))
1109 - }
1110 - oxc::Expression::TaggedTemplateExpression(t) => {
1111 - Expression::TaggedTemplateExpression(self.convert_tagged_template_expression(t))
1112 - }
1113 - oxc::Expression::ThisExpression(t) => Expression::ThisExpression(ThisExpression {
1114 - base: self.make_base_node(t.span),
1115 - }),
1116 - oxc::Expression::UnaryExpression(u) => {
1117 - Expression::UnaryExpression(self.convert_unary_expression(u))
1118 - }
1119 - oxc::Expression::UpdateExpression(u) => {
1120 - Expression::UpdateExpression(self.convert_update_expression(u))
1121 - }
1122 - oxc::Expression::YieldExpression(y) => {
1123 - Expression::YieldExpression(self.convert_yield_expression(y))
1124 - }
1125 - oxc::Expression::PrivateInExpression(_) => {
1126 - todo!("PrivateInExpression")
1127 - }
1128 - oxc::Expression::JSXElement(j) => {
1129 - Expression::JSXElement(Box::new(self.convert_jsx_element(j)))
1130 - }
1131 - oxc::Expression::JSXFragment(j) => {
1132 - Expression::JSXFragment(self.convert_jsx_fragment(j))
1133 - }
1134 - oxc::Expression::TSAsExpression(t) => {
1135 - Expression::TSAsExpression(self.convert_ts_as_expression(t))
1136 - }
1137 - oxc::Expression::TSSatisfiesExpression(t) => {
1138 - Expression::TSSatisfiesExpression(self.convert_ts_satisfies_expression(t))
1139 - }
1140 - oxc::Expression::TSTypeAssertion(t) => {
1141 - Expression::TSTypeAssertion(self.convert_ts_type_assertion(t))
1142 - }
1143 - oxc::Expression::TSNonNullExpression(t) => {
1144 - Expression::TSNonNullExpression(self.convert_ts_non_null_expression(t))
1145 - }
1146 - oxc::Expression::TSInstantiationExpression(t) => {
1147 - Expression::TSInstantiationExpression(self.convert_ts_instantiation_expression(t))
1148 - }
1149 - oxc::Expression::ComputedMemberExpression(m) => {
1150 - Expression::MemberExpression(MemberExpression {
1151 - base: self.make_base_node(m.span),
1152 - object: Box::new(self.convert_expression(&m.object)),
1153 - property: Box::new(self.convert_expression(&m.expression)),
1154 - computed: true,
1155 - })
1156 - }
1157 - oxc::Expression::StaticMemberExpression(m) => {
1158 - Expression::MemberExpression(MemberExpression {
1159 - base: self.make_base_node(m.span),
1160 - object: Box::new(self.convert_expression(&m.object)),
1161 - property: Box::new(Expression::Identifier(
1162 - self.convert_identifier_name(&m.property),
1163 - )),
1164 - computed: false,
1165 - })
1166 - }
1167 - oxc::Expression::PrivateFieldExpression(p) => {
1168 - Expression::MemberExpression(MemberExpression {
1169 - base: self.make_base_node(p.span),
1170 - object: Box::new(self.convert_expression(&p.object)),
1171 - property: Box::new(Expression::PrivateName(PrivateName {
1172 - base: self.make_base_node(p.field.span),
1173 - id: Identifier {
1174 - base: self.make_base_node(p.field.span),
1175 - name: p.field.name.to_string(),
1176 - type_annotation: None,
1177 - optional: None,
1178 - decorators: None,
1179 - },
1180 - })),
1181 - computed: false,
1182 - })
1183 - }
1184 - oxc::Expression::V8IntrinsicExpression(_) => {
1185 - todo!("V8IntrinsicExpression")
1186 - }
1187 - }
1188 - }
1189 -
1190 - fn convert_template_literal(&self, template: &oxc::TemplateLiteral) -> TemplateLiteral {
1191 - TemplateLiteral {
1192 - base: self.make_base_node(template.span),
1193 - quasis: template
1194 - .quasis
1195 - .iter()
1196 - .map(|q| self.convert_template_element(q))
1197 - .collect(),
1198 - expressions: template
1199 - .expressions
1200 - .iter()
1201 - .map(|e| self.convert_expression(e))
1202 - .collect(),
1203 - }
1204 - }
1205 -
1206 - fn convert_template_element(&self, element: &oxc::TemplateElement) -> TemplateElement {
1207 - TemplateElement {
1208 - base: self.make_base_node(element.span),
1209 - value: TemplateElementValue {
1210 - raw: element.value.raw.to_string(),
1211 - cooked: element.value.cooked.as_ref().map(|s| s.to_string()),
1212 - },
1213 - tail: element.tail,
1214 - }
1215 - }
1216 -
1217 - fn convert_meta_property(&self, meta: &oxc::MetaProperty) -> MetaProperty {
1218 - MetaProperty {
1219 - base: self.make_base_node(meta.span),
1220 - meta: self.convert_identifier_name(&meta.meta),
1221 - property: self.convert_identifier_name(&meta.property),
1222 - }
1223 - }
1224 -
1225 - fn convert_array_expression(&self, array: &oxc::ArrayExpression) -> ArrayExpression {
1226 - ArrayExpression {
1227 - base: self.make_base_node(array.span),
1228 - elements: array
1229 - .elements
1230 - .iter()
1231 - .map(|e| match e {
1232 - oxc::ArrayExpressionElement::SpreadElement(s) => {
1233 - Some(Expression::SpreadElement(SpreadElement {
1234 - base: self.make_base_node(s.span),
1235 - argument: Box::new(self.convert_expression(&s.argument)),
1236 - }))
1237 - }
1238 - oxc::ArrayExpressionElement::Elision(_) => None,
1239 - other => Some(self.convert_expression_from_array_element(other)),
1240 - })
1241 - .collect(),
1242 - }
1243 - }
1244 -
1245 - fn convert_arrow_function_expression(
1246 - &self,
1247 - arrow: &oxc::ArrowFunctionExpression,
1248 - ) -> ArrowFunctionExpression {
1249 - let body = if arrow.expression {
1250 - // When expression is true, the body contains a single expression statement
1251 - let expr = match &arrow.body.statements[0] {
1252 - oxc::Statement::ExpressionStatement(es) => self.convert_expression(&es.expression),
1253 - _ => panic!("Expected ExpressionStatement in arrow expression body"),
1254 - };
1255 - ArrowFunctionBody::Expression(Box::new(expr))
1256 - } else {
1257 - ArrowFunctionBody::BlockStatement(self.convert_function_body(&arrow.body))
1258 - };
1259 -
1260 - ArrowFunctionExpression {
1261 - base: self.make_base_node(arrow.span),
1262 - params: self.convert_formal_parameters(&arrow.params),
1263 - body: Box::new(body),
1264 - id: None,
1265 - generator: false,
1266 - is_async: arrow.r#async,
1267 - expression: if arrow.expression { Some(true) } else { None },
1268 - return_type: arrow
1269 - .return_type
1270 - .as_ref()
1271 - .map(|_t| RawNode::null()),
1272 - type_parameters: arrow
1273 - .type_parameters
1274 - .as_ref()
1275 - .map(|_t| RawNode::null()),
1276 - predicate: None,
1277 - }
1278 - }
1279 -
1280 - fn convert_assignment_expression(
1281 - &self,
1282 - assign: &oxc::AssignmentExpression,
1283 - ) -> AssignmentExpression {
1284 - AssignmentExpression {
1285 - base: self.make_base_node(assign.span),
1286 - operator: self.convert_assignment_operator(assign.operator),
1287 - left: Box::new(self.convert_assignment_target(&assign.left)),
1288 - right: Box::new(self.convert_expression(&assign.right)),
1289 - }
1290 - }
1291 -
1292 - fn convert_assignment_operator(&self, op: oxc::AssignmentOperator) -> AssignmentOperator {
1293 - match op {
1294 - oxc::AssignmentOperator::Assign => AssignmentOperator::Assign,
1295 - oxc::AssignmentOperator::Addition => AssignmentOperator::AddAssign,
1296 - oxc::AssignmentOperator::Subtraction => AssignmentOperator::SubAssign,
1297 - oxc::AssignmentOperator::Multiplication => AssignmentOperator::MulAssign,
1298 - oxc::AssignmentOperator::Division => AssignmentOperator::DivAssign,
1299 - oxc::AssignmentOperator::Remainder => AssignmentOperator::RemAssign,
1300 - oxc::AssignmentOperator::ShiftLeft => AssignmentOperator::ShlAssign,
1301 - oxc::AssignmentOperator::ShiftRight => AssignmentOperator::ShrAssign,
1302 - oxc::AssignmentOperator::ShiftRightZeroFill => AssignmentOperator::UShrAssign,
1303 - oxc::AssignmentOperator::BitwiseOR => AssignmentOperator::BitOrAssign,
1304 - oxc::AssignmentOperator::BitwiseXOR => AssignmentOperator::BitXorAssign,
1305 - oxc::AssignmentOperator::BitwiseAnd => AssignmentOperator::BitAndAssign,
1306 - oxc::AssignmentOperator::LogicalAnd => AssignmentOperator::AndAssign,
1307 - oxc::AssignmentOperator::LogicalOr => AssignmentOperator::OrAssign,
1308 - oxc::AssignmentOperator::LogicalNullish => AssignmentOperator::NullishAssign,
1309 - oxc::AssignmentOperator::Exponential => AssignmentOperator::ExpAssign,
1310 - }
1311 - }
1312 -
1313 - fn convert_assignment_target(&self, target: &oxc::AssignmentTarget) -> PatternLike {
1314 - match target {
1315 - oxc::AssignmentTarget::AssignmentTargetIdentifier(id) => {
1316 - PatternLike::Identifier(Identifier {
1317 - base: self.make_base_node(id.span),
1318 - name: id.name.to_string(),
1319 - type_annotation: None,
1320 - optional: None,
1321 - decorators: None,
1322 - })
1323 - }
1324 - oxc::AssignmentTarget::ComputedMemberExpression(m) => {
1325 - PatternLike::MemberExpression(MemberExpression {
1326 - base: self.make_base_node(m.span),
1327 - object: Box::new(self.convert_expression(&m.object)),
1328 - property: Box::new(self.convert_expression(&m.expression)),
1329 - computed: true,
1330 - })
1331 - }
1332 - oxc::AssignmentTarget::StaticMemberExpression(m) => {
1333 - PatternLike::MemberExpression(MemberExpression {
1334 - base: self.make_base_node(m.span),
1335 - object: Box::new(self.convert_expression(&m.object)),
1336 - property: Box::new(Expression::Identifier(
1337 - self.convert_identifier_name(&m.property),
1338 - )),
1339 - computed: false,
1340 - })
1341 - }
1342 - oxc::AssignmentTarget::PrivateFieldExpression(p) => {
1343 - PatternLike::MemberExpression(MemberExpression {
1344 - base: self.make_base_node(p.span),
1345 - object: Box::new(self.convert_expression(&p.object)),
1346 - property: Box::new(Expression::PrivateName(PrivateName {
1347 - base: self.make_base_node(p.field.span),
1348 - id: Identifier {
1349 - base: self.make_base_node(p.field.span),
1350 - name: p.field.name.to_string(),
1351 - type_annotation: None,
1352 - optional: None,
1353 - decorators: None,
1354 - },
1355 - })),
1356 - computed: false,
1357 - })
1358 - }
1359 - oxc::AssignmentTarget::ArrayAssignmentTarget(a) => {
1360 - self.convert_array_assignment_target(a)
1361 - }
1362 - oxc::AssignmentTarget::ObjectAssignmentTarget(o) => {
1363 - self.convert_object_assignment_target(o)
1364 - }
1365 - oxc::AssignmentTarget::TSAsExpression(_)
1366 - | oxc::AssignmentTarget::TSSatisfiesExpression(_)
1367 - | oxc::AssignmentTarget::TSNonNullExpression(_)
1368 - | oxc::AssignmentTarget::TSTypeAssertion(_) => {
1369 - todo!("TypeScript expression in assignment target")
1370 - }
1371 - }
1372 - }
1373 -
1374 - fn convert_array_assignment_target(&self, arr: &oxc::ArrayAssignmentTarget) -> PatternLike {
1375 - PatternLike::ArrayPattern(ArrayPattern {
1376 - base: self.make_base_node(arr.span),
1377 - elements: arr
1378 - .elements
1379 - .iter()
1380 - .map(|e| match e {
1381 - Some(oxc::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(d)) => {
1382 - Some(PatternLike::AssignmentPattern(AssignmentPattern {
1383 - base: self.make_base_node(d.span),
1384 - left: Box::new(self.convert_assignment_target(&d.binding)),
1385 - right: Box::new(self.convert_expression(&d.init)),
1386 - type_annotation: None,
1387 - decorators: None,
1388 - }))
1389 - }
1390 - Some(other) => {
1391 - Some(self.convert_assignment_target_maybe_default_as_target(other))
1392 - }
1393 - None => None,
1394 - })
1395 - .collect(),
1396 - type_annotation: None,
1397 - decorators: None,
1398 - })
1399 - }
1400 -
1401 - /// Convert an AssignmentTargetMaybeDefault that is NOT an AssignmentTargetWithDefault
1402 - /// to a PatternLike by extracting the underlying AssignmentTarget
1403 - fn convert_assignment_target_maybe_default_as_target(
1404 - &self,
1405 - target: &oxc::AssignmentTargetMaybeDefault,
1406 - ) -> PatternLike {
1407 - match target {
1408 - oxc::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(_) => {
1409 - unreachable!("handled separately")
1410 - }
1411 - oxc::AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(id) => {
1412 - PatternLike::Identifier(Identifier {
1413 - base: self.make_base_node(id.span),
1414 - name: id.name.to_string(),
1415 - type_annotation: None,
1416 - optional: None,
1417 - decorators: None,
1418 - })
1419 - }
1420 - oxc::AssignmentTargetMaybeDefault::ComputedMemberExpression(m) => {
1421 - PatternLike::MemberExpression(MemberExpression {
1422 - base: self.make_base_node(m.span),
1423 - object: Box::new(self.convert_expression(&m.object)),
1424 - property: Box::new(self.convert_expression(&m.expression)),
1425 - computed: true,
1426 - })
1427 - }
1428 - oxc::AssignmentTargetMaybeDefault::StaticMemberExpression(m) => {
1429 - PatternLike::MemberExpression(MemberExpression {
1430 - base: self.make_base_node(m.span),
1431 - object: Box::new(self.convert_expression(&m.object)),
1432 - property: Box::new(Expression::Identifier(
1433 - self.convert_identifier_name(&m.property),
1434 - )),
1435 - computed: false,
1436 - })
1437 - }
1438 - oxc::AssignmentTargetMaybeDefault::PrivateFieldExpression(p) => {
1439 - PatternLike::MemberExpression(MemberExpression {
1440 - base: self.make_base_node(p.span),
1441 - object: Box::new(self.convert_expression(&p.object)),
1442 - property: Box::new(Expression::PrivateName(PrivateName {
1443 - base: self.make_base_node(p.field.span),
1444 - id: Identifier {
1445 - base: self.make_base_node(p.field.span),
1446 - name: p.field.name.to_string(),
1447 - type_annotation: None,
1448 - optional: None,
1449 - decorators: None,
1450 - },
1451 - })),
1452 - computed: false,
1453 - })
1454 - }
1455 - oxc::AssignmentTargetMaybeDefault::ArrayAssignmentTarget(a) => {
1456 - self.convert_array_assignment_target(a)
1457 - }
1458 - oxc::AssignmentTargetMaybeDefault::ObjectAssignmentTarget(o) => {
1459 - self.convert_object_assignment_target(o)
1460 - }
1461 - oxc::AssignmentTargetMaybeDefault::TSAsExpression(_)
1462 - | oxc::AssignmentTargetMaybeDefault::TSSatisfiesExpression(_)
1463 - | oxc::AssignmentTargetMaybeDefault::TSNonNullExpression(_)
1464 - | oxc::AssignmentTargetMaybeDefault::TSTypeAssertion(_) => {
1465 - todo!("TypeScript expression in assignment target")
1466 - }
1467 - }
1468 - }
1469 -
1470 - fn convert_object_assignment_target(&self, obj: &oxc::ObjectAssignmentTarget) -> PatternLike {
1471 - let mut properties: Vec<ObjectPatternProperty> = obj
1472 - .properties
1473 - .iter()
1474 - .map(|p| match p {
1475 - oxc::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => {
1476 - let ident = PatternLike::Identifier(Identifier {
1477 - base: self.make_base_node(id.binding.span),
1478 - name: id.binding.name.to_string(),
1479 - type_annotation: None,
1480 - optional: None,
1481 - decorators: None,
1482 - });
1483 - let value = if let Some(init) = &id.init {
1484 - Box::new(PatternLike::AssignmentPattern(AssignmentPattern {
1485 - base: self.make_base_node(id.span),
1486 - left: Box::new(ident),
1487 - right: Box::new(self.convert_expression(init)),
1488 - type_annotation: None,
1489 - decorators: None,
1490 - }))
1491 - } else {
1492 - Box::new(ident)
1493 - };
1494 - ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
1495 - base: self.make_base_node(id.span),
1496 - key: Box::new(Expression::Identifier(Identifier {
1497 - base: self.make_base_node(id.binding.span),
1498 - name: id.binding.name.to_string(),
1499 - type_annotation: None,
1500 - optional: None,
1501 - decorators: None,
1502 - })),
1503 - value,
1504 - computed: false,
1505 - shorthand: true,
1506 - decorators: None,
1507 - method: None,
1508 - })
1509 - }
1510 - oxc::AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => {
1511 - let value = match &prop.binding {
1512 - oxc::AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(d) => {
1513 - Box::new(PatternLike::AssignmentPattern(AssignmentPattern {
1514 - base: self.make_base_node(d.span),
1515 - left: Box::new(self.convert_assignment_target(&d.binding)),
1516 - right: Box::new(self.convert_expression(&d.init)),
1517 - type_annotation: None,
1518 - decorators: None,
1519 - }))
1520 - }
1521 - other => {
1522 - Box::new(self.convert_assignment_target_maybe_default_as_target(other))
1523 - }
1524 - };
1525 - ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
1526 - base: self.make_base_node(prop.span),
1527 - key: Box::new(self.convert_property_key(&prop.name)),
1528 - value,
1529 - computed: prop.computed,
1530 - shorthand: false,
1531 - decorators: None,
1532 - method: None,
1533 - })
1534 - }
1535 - })
1536 - .collect();
1537 -
1538 - // Handle rest element separately (it's now a separate field)
1539 - if let Some(rest) = &obj.rest {
1540 - properties.push(ObjectPatternProperty::RestElement(RestElement {
1541 - base: self.make_base_node(rest.span),
1542 - argument: Box::new(self.convert_assignment_target(&rest.target)),
1543 - type_annotation: None,
1544 - decorators: None,
1545 - }));
1546 - }
1547 -
1548 - PatternLike::ObjectPattern(ObjectPattern {
1549 - base: self.make_base_node(obj.span),
1550 - properties,
1551 - type_annotation: None,
1552 - decorators: None,
1553 - })
1554 - }
1555 -
1556 - fn convert_await_expression(&self, await_expr: &oxc::AwaitExpression) -> AwaitExpression {
1557 - AwaitExpression {
1558 - base: self.make_base_node(await_expr.span),
1559 - argument: Box::new(self.convert_expression(&await_expr.argument)),
1560 - }
1561 - }
1562 -
1563 - fn convert_binary_expression(&self, binary: &oxc::BinaryExpression) -> BinaryExpression {
1564 - BinaryExpression {
1565 - base: self.make_base_node(binary.span),
1566 - operator: self.convert_binary_operator(binary.operator),
1567 - left: Box::new(self.convert_expression(&binary.left)),
1568 - right: Box::new(self.convert_expression(&binary.right)),
1569 - }
1570 - }
1571 -
1572 - fn convert_binary_operator(&self, op: oxc::BinaryOperator) -> BinaryOperator {
1573 - match op {
1574 - oxc::BinaryOperator::Equality => BinaryOperator::Eq,
1575 - oxc::BinaryOperator::Inequality => BinaryOperator::Neq,
1576 - oxc::BinaryOperator::StrictEquality => BinaryOperator::StrictEq,
1577 - oxc::BinaryOperator::StrictInequality => BinaryOperator::StrictNeq,
1578 - oxc::BinaryOperator::LessThan => BinaryOperator::Lt,
1579 - oxc::BinaryOperator::LessEqualThan => BinaryOperator::Lte,
1580 - oxc::BinaryOperator::GreaterThan => BinaryOperator::Gt,
1581 - oxc::BinaryOperator::GreaterEqualThan => BinaryOperator::Gte,
1582 - oxc::BinaryOperator::ShiftLeft => BinaryOperator::Shl,
1583 - oxc::BinaryOperator::ShiftRight => BinaryOperator::Shr,
1584 - oxc::BinaryOperator::ShiftRightZeroFill => BinaryOperator::UShr,
1585 - oxc::BinaryOperator::Addition => BinaryOperator::Add,
1586 - oxc::BinaryOperator::Subtraction => BinaryOperator::Sub,
1587 - oxc::BinaryOperator::Multiplication => BinaryOperator::Mul,
1588 - oxc::BinaryOperator::Division => BinaryOperator::Div,
1589 - oxc::BinaryOperator::Remainder => BinaryOperator::Rem,
1590 - oxc::BinaryOperator::BitwiseOR => BinaryOperator::BitOr,
1591 - oxc::BinaryOperator::BitwiseXOR => BinaryOperator::BitXor,
1592 - oxc::BinaryOperator::BitwiseAnd => BinaryOperator::BitAnd,
1593 - oxc::BinaryOperator::In => BinaryOperator::In,
1594 - oxc::BinaryOperator::Instanceof => BinaryOperator::Instanceof,
1595 - oxc::BinaryOperator::Exponential => BinaryOperator::Exp,
1596 - }
1597 - }
1598 -
1599 - fn convert_call_expression(&self, call: &oxc::CallExpression) -> CallExpression {
1600 - CallExpression {
1601 - base: self.make_base_node(call.span),
1602 - callee: Box::new(self.convert_expression(&call.callee)),
1603 - arguments: call
1604 - .arguments
1605 - .iter()
1606 - .map(|a| self.convert_argument(a))
1607 - .collect(),
1608 - type_parameters: call
1609 - .type_arguments
1610 - .as_ref()
1611 - .map(|_t| RawNode::null()),
1612 - type_arguments: None,
1613 - optional: if call.optional { Some(true) } else { None },
1614 - }
1615 - }
1616 -
1617 - fn convert_argument(&self, arg: &oxc::Argument) -> Expression {
1618 - match arg {
1619 - oxc::Argument::SpreadElement(s) => Expression::SpreadElement(SpreadElement {
1620 - base: self.make_base_node(s.span),
1621 - argument: Box::new(self.convert_expression(&s.argument)),
1622 - }),
1623 - other => self.convert_expression_from_argument(other),
1624 - }
1625 - }
1626 -
1627 - fn convert_chain_expression(&self, chain: &oxc::ChainExpression) -> Expression {
1628 - // ChainExpression wraps optional call/member expressions in Babel
1629 - match &chain.expression {
1630 - oxc::ChainElement::CallExpression(c) => {
1631 - Expression::OptionalCallExpression(OptionalCallExpression {
1632 - base: self.make_base_node(c.span),
1633 - callee: Box::new(self.convert_expression_in_chain(&c.callee)),
1634 - arguments: c
1635 - .arguments
1636 - .iter()
1637 - .map(|a| self.convert_argument(a))
1638 - .collect(),
1639 - optional: c.optional,
1640 - type_parameters: c
1641 - .type_arguments
1642 - .as_ref()
1643 - .map(|_t| RawNode::null()),
1644 - type_arguments: None,
1645 - })
1646 - }
1647 - oxc::ChainElement::ComputedMemberExpression(m) => {
1648 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1649 - base: self.make_base_node(m.span),
1650 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1651 - property: Box::new(self.convert_expression(&m.expression)),
1652 - computed: true,
1653 - optional: m.optional,
1654 - })
1655 - }
1656 - oxc::ChainElement::StaticMemberExpression(m) => {
1657 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1658 - base: self.make_base_node(m.span),
1659 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1660 - property: Box::new(Expression::Identifier(
1661 - self.convert_identifier_name(&m.property),
1662 - )),
1663 - computed: false,
1664 - optional: m.optional,
1665 - })
1666 - }
1667 - oxc::ChainElement::PrivateFieldExpression(p) => {
1668 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1669 - base: self.make_base_node(p.span),
1670 - object: Box::new(self.convert_expression_in_chain(&p.object)),
1671 - property: Box::new(Expression::PrivateName(PrivateName {
1672 - base: self.make_base_node(p.field.span),
1673 - id: Identifier {
1674 - base: self.make_base_node(p.field.span),
1675 - name: p.field.name.to_string(),
1676 - type_annotation: None,
1677 - optional: None,
1678 - decorators: None,
1679 - },
1680 - })),
1681 - computed: false,
1682 - optional: p.optional,
1683 - })
1684 - }
1685 - oxc::ChainElement::TSNonNullExpression(_) => {
1686 - todo!("TSNonNullExpression in chain expression")
1687 - }
1688 - }
1689 - }
1690 -
1691 - /// Check if an expression within a chain contains any optional access.
1692 - fn expr_contains_optional(expr: &oxc::Expression) -> bool {
1693 - match expr {
1694 - oxc::Expression::CallExpression(c) => {
1695 - c.optional || Self::expr_contains_optional(&c.callee)
1696 - }
1697 - oxc::Expression::StaticMemberExpression(m) => {
1698 - m.optional || Self::expr_contains_optional(&m.object)
1699 - }
1700 - oxc::Expression::ComputedMemberExpression(m) => {
1701 - m.optional || Self::expr_contains_optional(&m.object)
1702 - }
1703 - oxc::Expression::PrivateFieldExpression(p) => {
1704 - p.optional || Self::expr_contains_optional(&p.object)
1705 - }
1706 - _ => false,
1707 - }
1708 - }
1709 -
1710 - /// Convert an expression that appears as callee/object inside a ChainExpression.
1711 - /// In OXC, `a?.b?.c` is a single ChainExpression with nested CallExpression/
1712 - /// MemberExpression nodes that have `optional: true`. In Babel, each optional
1713 - /// node is an OptionalCallExpression/OptionalMemberExpression. Non-optional
1714 - /// nodes that appear AFTER the first `?.` become OptionalMember/Call with
1715 - /// `optional: false`, while non-optional nodes BEFORE the first `?.` are
1716 - /// regular MemberExpression/CallExpression nodes.
1717 - fn convert_expression_in_chain(&self, expr: &oxc::Expression) -> Expression {
1718 - match expr {
1719 - oxc::Expression::CallExpression(c) if c.optional => {
1720 - Expression::OptionalCallExpression(OptionalCallExpression {
1721 - base: self.make_base_node(c.span),
1722 - callee: Box::new(self.convert_expression_in_chain(&c.callee)),
1723 - arguments: c
1724 - .arguments
1725 - .iter()
1726 - .map(|a| self.convert_argument(a))
1727 - .collect(),
1728 - optional: true,
1729 - type_parameters: c
1730 - .type_arguments
1731 - .as_ref()
1732 - .map(|_t| RawNode::null()),
1733 - type_arguments: None,
1734 - })
1735 - }
1736 - oxc::Expression::StaticMemberExpression(m) if m.optional => {
1737 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1738 - base: self.make_base_node(m.span),
1739 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1740 - property: Box::new(Expression::Identifier(
1741 - self.convert_identifier_name(&m.property),
1742 - )),
1743 - computed: false,
1744 - optional: true,
1745 - })
1746 - }
1747 - oxc::Expression::ComputedMemberExpression(m) if m.optional => {
1748 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1749 - base: self.make_base_node(m.span),
1750 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1751 - property: Box::new(self.convert_expression(&m.expression)),
1752 - computed: true,
1753 - optional: true,
1754 - })
1755 - }
1756 - // Non-optional expressions inside chains: need to check if the
1757 - // object/callee still contains optional parts. If so, this node
1758 - // is AFTER the first `?.` and should be OptionalMember/Call with
1759 - // optional: false. If not, it's BEFORE the first `?.` and should
1760 - // be a regular MemberExpression/CallExpression.
1761 - oxc::Expression::CallExpression(c) if Self::expr_contains_optional(&c.callee) => {
1762 - Expression::OptionalCallExpression(OptionalCallExpression {
1763 - base: self.make_base_node(c.span),
1764 - callee: Box::new(self.convert_expression_in_chain(&c.callee)),
1765 - arguments: c
1766 - .arguments
1767 - .iter()
1768 - .map(|a| self.convert_argument(a))
1769 - .collect(),
1770 - optional: false,
1771 - type_parameters: c
1772 - .type_arguments
1773 - .as_ref()
1774 - .map(|_t| RawNode::null()),
1775 - type_arguments: None,
1776 - })
1777 - }
1778 - oxc::Expression::StaticMemberExpression(m)
1779 - if Self::expr_contains_optional(&m.object) =>
1780 - {
1781 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1782 - base: self.make_base_node(m.span),
1783 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1784 - property: Box::new(Expression::Identifier(
1785 - self.convert_identifier_name(&m.property),
1786 - )),
1787 - computed: false,
1788 - optional: false,
1789 - })
1790 - }
1791 - oxc::Expression::ComputedMemberExpression(m)
1792 - if Self::expr_contains_optional(&m.object) =>
1793 - {
1794 - Expression::OptionalMemberExpression(OptionalMemberExpression {
1795 - base: self.make_base_node(m.span),
1796 - object: Box::new(self.convert_expression_in_chain(&m.object)),
1797 - property: Box::new(self.convert_expression(&m.expression)),
1798 - computed: true,
1799 - optional: false,
1800 - })
1801 - }
1802 - // No optional in the sub-tree — this is the base receiver before
1803 - // the first `?.`. Convert as a regular expression.
1804 - _ => self.convert_expression(expr),
1805 - }
1806 - }
1807 -
1808 - fn convert_class_expression(&self, class: &oxc::Class) -> ClassExpression {
1809 - ClassExpression {
1810 - base: self.make_base_node(class.span),
1811 - id: class
1812 - .id
1813 - .as_ref()
1814 - .map(|id| self.convert_binding_identifier(id)),
1815 - super_class: class
1816 - .super_class
1817 - .as_ref()
1818 - .map(|s| Box::new(self.convert_expression(s))),
1819 - body: ClassBody {
1820 - base: self.make_base_node(class.body.span),
1821 - body: class
1822 - .body
1823 - .body
1824 - .iter()
1825 - .map(|_item| RawNode::null())
1826 - .collect(),
1827 - },
1828 - decorators: if class.decorators.is_empty() {
1829 - None
1830 - } else {
1831 - Some(
1832 - class
1833 - .decorators
1834 - .iter()
1835 - .map(|_d| RawNode::null())
1836 - .collect(),
1837 - )
1838 - },
1839 - implements: if class.implements.is_empty() {
1840 - None
1841 - } else {
1842 - Some(
1843 - class
1844 - .implements
1845 - .iter()
1846 - .map(|_i| RawNode::null())
1847 - .collect(),
1848 - )
1849 - },
1850 - super_type_parameters: class
1851 - .super_type_arguments
1852 - .as_ref()
1853 - .map(|_t| RawNode::null()),
1854 - type_parameters: class
1855 - .type_parameters
1856 - .as_ref()
1857 - .map(|_t| RawNode::null()),
1858 - }
1859 - }
1860 -
1861 - fn convert_conditional_expression(
1862 - &self,
1863 - cond: &oxc::ConditionalExpression,
1864 - ) -> ConditionalExpression {
1865 - ConditionalExpression {
1866 - base: self.make_base_node(cond.span),
1867 - test: Box::new(self.convert_expression(&cond.test)),
1868 - consequent: Box::new(self.convert_expression(&cond.consequent)),
1869 - alternate: Box::new(self.convert_expression(&cond.alternate)),
1870 - }
1871 - }
1872 -
1873 - fn convert_function_expression(&self, func: &oxc::Function) -> FunctionExpression {
1874 - FunctionExpression {
1875 - base: self.make_base_node(func.span),
1876 - id: func
1877 - .id
1878 - .as_ref()
1879 - .map(|id| self.convert_binding_identifier(id)),
1880 - params: self.convert_formal_parameters(&func.params),
1881 - body: self.convert_function_body(func.body.as_ref().unwrap()),
1882 - generator: func.generator,
1883 - is_async: func.r#async,
1884 - return_type: func
1885 - .return_type
1886 - .as_ref()
1887 - .map(|_t| RawNode::null()),
1888 - type_parameters: func
1889 - .type_parameters
1890 - .as_ref()
1891 - .map(|_t| RawNode::null()),
1892 - predicate: None,
1893 - }
1894 - }
1895 -
1896 - fn convert_logical_expression(&self, logical: &oxc::LogicalExpression) -> LogicalExpression {
1897 - LogicalExpression {
1898 - base: self.make_base_node(logical.span),
1899 - operator: self.convert_logical_operator(logical.operator),
1900 - left: Box::new(self.convert_expression(&logical.left)),
1901 - right: Box::new(self.convert_expression(&logical.right)),
1902 - }
1903 - }
1904 -
1905 - fn convert_logical_operator(&self, op: oxc::LogicalOperator) -> LogicalOperator {
1906 - match op {
1907 - oxc::LogicalOperator::Or => LogicalOperator::Or,
1908 - oxc::LogicalOperator::And => LogicalOperator::And,
1909 - oxc::LogicalOperator::Coalesce => LogicalOperator::NullishCoalescing,
1910 - }
1911 - }
1912 -
1913 - fn convert_new_expression(&self, new: &oxc::NewExpression) -> NewExpression {
1914 - NewExpression {
1915 - base: self.make_base_node(new.span),
1916 - callee: Box::new(self.convert_expression(&new.callee)),
1917 - arguments: new
1918 - .arguments
1919 - .iter()
1920 - .map(|a| self.convert_argument(a))
1921 - .collect(),
1922 - type_parameters: new
1923 - .type_arguments
1924 - .as_ref()
1925 - .map(|_t| RawNode::null()),
1926 - type_arguments: None,
1927 - }
1928 - }
1929 -
1930 - fn convert_object_expression(&self, obj: &oxc::ObjectExpression) -> ObjectExpression {
1931 - ObjectExpression {
1932 - base: self.make_base_node(obj.span),
1933 - properties: obj
1934 - .properties
1935 - .iter()
1936 - .map(|p| self.convert_object_property_kind(p))
1937 - .collect(),
1938 - }
1939 - }
1940 -
1941 - fn convert_object_property_kind(
1942 - &self,
1943 - prop: &oxc::ObjectPropertyKind,
1944 - ) -> ObjectExpressionProperty {
1945 - match prop {
1946 - oxc::ObjectPropertyKind::ObjectProperty(p) => {
1947 - // When method is true or kind is Get/Set, convert to ObjectMethod
1948 - // to match Babel's AST representation of method shorthand.
1949 - if p.method || matches!(p.kind, oxc::PropertyKind::Get | oxc::PropertyKind::Set) {
1950 - if let oxc::Expression::FunctionExpression(func) = &p.value {
1951 - let method_kind = match p.kind {
1952 - oxc::PropertyKind::Get => ObjectMethodKind::Get,
1953 - oxc::PropertyKind::Set => ObjectMethodKind::Set,
1954 - _ => ObjectMethodKind::Method,
1955 - };
1956 - let params: Vec<PatternLike> = self.convert_formal_parameters(&func.params);
1957 - let body = func
1958 - .body
1959 - .as_ref()
1960 - .map(|b| self.convert_function_body(b))
1961 - .unwrap_or_else(|| BlockStatement {
1962 - base: BaseNode::default(),
1963 - body: vec![],
1964 - directives: vec![],
1965 - });
1966 - return ObjectExpressionProperty::ObjectMethod(ObjectMethod {
1967 - base: self.make_base_node(p.span),
1968 - method: p.method,
1969 - kind: method_kind,
1970 - key: Box::new(self.convert_property_key(&p.key)),
1971 - params,
1972 - body,
1973 - computed: p.computed,
1974 - id: func
1975 - .id
1976 - .as_ref()
1977 - .map(|id| self.convert_binding_identifier(id)),
1978 - generator: func.generator,
1979 - is_async: func.r#async,
1980 - decorators: None,
1981 - return_type: func
1982 - .return_type
1983 - .as_ref()
1984 - .map(|_| RawNode::null()),
1985 - type_parameters: func
1986 - .type_parameters
1987 - .as_ref()
1988 - .map(|_| RawNode::null()),
1989 - predicate: None,
1990 - });
1991 - }
1992 - }
1993 - ObjectExpressionProperty::ObjectProperty(self.convert_object_property(p))
1994 - }
1995 - oxc::ObjectPropertyKind::SpreadProperty(s) => {
1996 - ObjectExpressionProperty::SpreadElement(SpreadElement {
1997 - base: self.make_base_node(s.span),
1998 - argument: Box::new(self.convert_expression(&s.argument)),
1999 - })
2000 - }
2001 - }
2002 - }
2003 -
2004 - fn convert_object_property(&self, prop: &oxc::ObjectProperty) -> ObjectProperty {
2005 - ObjectProperty {
2006 - base: self.make_base_node(prop.span),
2007 - key: Box::new(self.convert_property_key(&prop.key)),
2008 - value: Box::new(self.convert_expression(&prop.value)),
2009 - computed: prop.computed,
2010 - shorthand: prop.shorthand,
2011 - decorators: None,
2012 - method: if prop.method { Some(true) } else { None },
2013 - }
2014 - }
2015 -
2016 - fn convert_property_key(&self, key: &oxc::PropertyKey) -> Expression {
2017 - match key {
2018 - oxc::PropertyKey::StaticIdentifier(id) => {
2019 - Expression::Identifier(self.convert_identifier_name(id))
2020 - }
2021 - oxc::PropertyKey::PrivateIdentifier(id) => Expression::PrivateName(PrivateName {
2022 - base: self.make_base_node(id.span),
2023 - id: Identifier {
2024 - base: self.make_base_node(id.span),
2025 - name: id.name.to_string(),
2026 - type_annotation: None,
2027 - optional: None,
2028 - decorators: None,
2029 - },
2030 - }),
2031 - other => self.convert_expression_from_property_key(other),
2032 - }
2033 - }
2034 -
2035 - fn convert_parenthesized_expression(
2036 - &self,
2037 - paren: &oxc::ParenthesizedExpression,
2038 - ) -> ParenthesizedExpression {
2039 - ParenthesizedExpression {
2040 - base: self.make_base_node(paren.span),
2041 - expression: Box::new(self.convert_expression(&paren.expression)),
2042 - }
2043 - }
2044 -
2045 - fn convert_sequence_expression(&self, seq: &oxc::SequenceExpression) -> SequenceExpression {
2046 - SequenceExpression {
2047 - base: self.make_base_node(seq.span),
2048 - expressions: seq
2049 - .expressions
2050 - .iter()
2051 - .map(|e| self.convert_expression(e))
2052 - .collect(),
2053 - }
2054 - }
2055 -
2056 - fn convert_tagged_template_expression(
2057 - &self,
2058 - tagged: &oxc::TaggedTemplateExpression,
2059 - ) -> TaggedTemplateExpression {
2060 - TaggedTemplateExpression {
2061 - base: self.make_base_node(tagged.span),
2062 - tag: Box::new(self.convert_expression(&tagged.tag)),
2063 - quasi: self.convert_template_literal(&tagged.quasi),
2064 - type_parameters: tagged
2065 - .type_arguments
2066 - .as_ref()
2067 - .map(|_t| RawNode::null()),
2068 - }
2069 - }
2070 -
2071 - fn convert_unary_expression(&self, unary: &oxc::UnaryExpression) -> UnaryExpression {
2072 - UnaryExpression {
2073 - base: self.make_base_node(unary.span),
2074 - operator: self.convert_unary_operator(unary.operator),
2075 - prefix: true,
2076 - argument: Box::new(self.convert_expression(&unary.argument)),
2077 - }
2078 - }
2079 -
2080 - fn convert_unary_operator(&self, op: oxc::UnaryOperator) -> UnaryOperator {
2081 - match op {
2082 - oxc::UnaryOperator::UnaryNegation => UnaryOperator::Neg,
2083 - oxc::UnaryOperator::UnaryPlus => UnaryOperator::Plus,
2084 - oxc::UnaryOperator::LogicalNot => UnaryOperator::Not,
2085 - oxc::UnaryOperator::BitwiseNot => UnaryOperator::BitNot,
2086 - oxc::UnaryOperator::Typeof => UnaryOperator::TypeOf,
2087 - oxc::UnaryOperator::Void => UnaryOperator::Void,
2088 - oxc::UnaryOperator::Delete => UnaryOperator::Delete,
2089 - }
2090 - }
2091 -
2092 - fn convert_update_expression(&self, update: &oxc::UpdateExpression) -> UpdateExpression {
2093 - UpdateExpression {
2094 - base: self.make_base_node(update.span),
2095 - operator: self.convert_update_operator(update.operator),
2096 - argument: Box::new(
2097 - self.convert_simple_assignment_target_as_expression(&update.argument),
2098 - ),
2099 - prefix: update.prefix,
2100 - }
2101 - }
2102 -
2103 - fn convert_simple_assignment_target_as_expression(
2104 - &self,
2105 - target: &oxc::SimpleAssignmentTarget,
2106 - ) -> Expression {
2107 - match target {
2108 - oxc::SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
2109 - Expression::Identifier(Identifier {
2110 - base: self.make_base_node(id.span),
2111 - name: id.name.to_string(),
2112 - type_annotation: None,
2113 - optional: None,
2114 - decorators: None,
2115 - })
2116 - }
2117 - oxc::SimpleAssignmentTarget::ComputedMemberExpression(m) => {
2118 - Expression::MemberExpression(MemberExpression {
2119 - base: self.make_base_node(m.span),
2120 - object: Box::new(self.convert_expression(&m.object)),
2121 - property: Box::new(self.convert_expression(&m.expression)),
2122 - computed: true,
2123 - })
2124 - }
2125 - oxc::SimpleAssignmentTarget::StaticMemberExpression(m) => {
2126 - Expression::MemberExpression(MemberExpression {
2127 - base: self.make_base_node(m.span),
2128 - object: Box::new(self.convert_expression(&m.object)),
2129 - property: Box::new(Expression::Identifier(
2130 - self.convert_identifier_name(&m.property),
2131 - )),
2132 - computed: false,
2133 - })
2134 - }
2135 - oxc::SimpleAssignmentTarget::PrivateFieldExpression(p) => {
2136 - Expression::MemberExpression(MemberExpression {
2137 - base: self.make_base_node(p.span),
2138 - object: Box::new(self.convert_expression(&p.object)),
2139 - property: Box::new(Expression::PrivateName(PrivateName {
2140 - base: self.make_base_node(p.field.span),
2141 - id: Identifier {
2142 - base: self.make_base_node(p.field.span),
2143 - name: p.field.name.to_string(),
2144 - type_annotation: None,
2145 - optional: None,
2146 - decorators: None,
2147 - },
2148 - })),
2149 - computed: false,
2150 - })
2151 - }
2152 - oxc::SimpleAssignmentTarget::TSAsExpression(t) => {
2153 - self.convert_expression(&t.expression)
2154 - }
2155 - oxc::SimpleAssignmentTarget::TSSatisfiesExpression(t) => {
2156 - self.convert_expression(&t.expression)
2157 - }
2158 - oxc::SimpleAssignmentTarget::TSNonNullExpression(t) => {
2159 - self.convert_expression(&t.expression)
2160 - }
2161 - oxc::SimpleAssignmentTarget::TSTypeAssertion(t) => {
2162 - self.convert_expression(&t.expression)
2163 - }
2164 - }
2165 - }
2166 -
2167 - fn convert_update_operator(&self, op: oxc::UpdateOperator) -> UpdateOperator {
2168 - match op {
2169 - oxc::UpdateOperator::Increment => UpdateOperator::Increment,
2170 - oxc::UpdateOperator::Decrement => UpdateOperator::Decrement,
2171 - }
2172 - }
2173 -
2174 - fn convert_yield_expression(&self, yield_expr: &oxc::YieldExpression) -> YieldExpression {
2175 - YieldExpression {
2176 - base: self.make_base_node(yield_expr.span),
2177 - argument: yield_expr
2178 - .argument
2179 - .as_ref()
2180 - .map(|a| Box::new(self.convert_expression(a))),
2181 - delegate: yield_expr.delegate,
2182 - }
2183 - }
2184 -
2185 - fn convert_ts_as_expression(&self, ts_as: &oxc::TSAsExpression) -> TSAsExpression {
2186 - TSAsExpression {
2187 - base: self.make_base_node(ts_as.span),
2188 - expression: Box::new(self.convert_expression(&ts_as.expression)),
2189 - type_annotation: RawNode::null(),
2190 - }
2191 - }
2192 -
2193 - fn convert_ts_satisfies_expression(
2194 - &self,
2195 - ts_sat: &oxc::TSSatisfiesExpression,
2196 - ) -> TSSatisfiesExpression {
2197 - TSSatisfiesExpression {
2198 - base: self.make_base_node(ts_sat.span),
2199 - expression: Box::new(self.convert_expression(&ts_sat.expression)),
2200 - type_annotation: RawNode::null(),
2201 - }
2202 - }
2203 -
2204 - fn convert_ts_type_assertion(&self, ts_assert: &oxc::TSTypeAssertion) -> TSTypeAssertion {
2205 - TSTypeAssertion {
2206 - base: self.make_base_node(ts_assert.span),
2207 - expression: Box::new(self.convert_expression(&ts_assert.expression)),
2208 - type_annotation: RawNode::null(),
2209 - }
2210 - }
2211 -
2212 - fn convert_ts_non_null_expression(
2213 - &self,
2214 - ts_non_null: &oxc::TSNonNullExpression,
2215 - ) -> TSNonNullExpression {
2216 - TSNonNullExpression {
2217 - base: self.make_base_node(ts_non_null.span),
2218 - expression: Box::new(self.convert_expression(&ts_non_null.expression)),
2219 - }
2220 - }
2221 -
2222 - fn convert_ts_instantiation_expression(
2223 - &self,
2224 - ts_inst: &oxc::TSInstantiationExpression,
2225 - ) -> TSInstantiationExpression {
2226 - TSInstantiationExpression {
2227 - base: self.make_base_node(ts_inst.span),
2228 - expression: Box::new(self.convert_expression(&ts_inst.expression)),
2229 - type_parameters: RawNode::null(),
2230 - }
2231 - }
2232 -
2233 - fn convert_jsx_element(&self, jsx: &oxc::JSXElement) -> JSXElement {
2234 - JSXElement {
2235 - base: self.make_base_node(jsx.span),
2236 - opening_element: self.convert_jsx_opening_element(&jsx.opening_element),
2237 - closing_element: jsx
2238 - .closing_element
2239 - .as_ref()
2240 - .map(|c| self.convert_jsx_closing_element(c)),
2241 - children: jsx
2242 - .children
2243 - .iter()
2244 - .map(|c| self.convert_jsx_child(c))
2245 - .collect(),
2246 - self_closing: None,
2247 - }
2248 - }
2249 -
2250 - fn convert_jsx_fragment(&self, jsx: &oxc::JSXFragment) -> JSXFragment {
2251 - JSXFragment {
2252 - base: self.make_base_node(jsx.span),
2253 - opening_fragment: JSXOpeningFragment {
2254 - base: self.make_base_node(jsx.opening_fragment.span),
2255 - },
2256 - closing_fragment: JSXClosingFragment {
2257 - base: self.make_base_node(jsx.closing_fragment.span),
2258 - },
2259 - children: jsx
2260 - .children
2261 - .iter()
2262 - .map(|c| self.convert_jsx_child(c))
2263 - .collect(),
2264 - }
2265 - }
2266 -
2267 - fn convert_jsx_opening_element(&self, opening: &oxc::JSXOpeningElement) -> JSXOpeningElement {
2268 - // In OXC v0.121, self_closing is computed (not a field), and type_parameters -> type_arguments
2269 - // Determine self_closing from absence of closing element (handled at JSXElement level)
2270 - JSXOpeningElement {
2271 - base: self.make_base_node(opening.span),
2272 - name: self.convert_jsx_element_name(&opening.name),
2273 - attributes: opening
2274 - .attributes
2275 - .iter()
2276 - .map(|a| self.convert_jsx_attribute_item(a))
2277 - .collect(),
2278 - self_closing: false, // Will be set properly at JSXElement level if needed
2279 - type_parameters: opening
2280 - .type_arguments
2281 - .as_ref()
2282 - .map(|_t| RawNode::null()),
2283 - }
2284 - }
2285 -
2286 - fn convert_jsx_closing_element(&self, closing: &oxc::JSXClosingElement) -> JSXClosingElement {
2287 - JSXClosingElement {
2288 - base: self.make_base_node(closing.span),
2289 - name: self.convert_jsx_element_name(&closing.name),
2290 - }
2291 - }
2292 -
2293 - fn convert_jsx_element_name(&self, name: &oxc::JSXElementName) -> JSXElementName {
2294 - match name {
2295 - oxc::JSXElementName::Identifier(id) => JSXElementName::JSXIdentifier(JSXIdentifier {
2296 - base: self.make_base_node(id.span),
2297 - name: id.name.to_string(),
2298 - }),
2299 - oxc::JSXElementName::IdentifierReference(id) => {
2300 - JSXElementName::JSXIdentifier(JSXIdentifier {
2301 - base: self.make_base_node(id.span),
2302 - name: id.name.to_string(),
2303 - })
2304 - }
2305 - oxc::JSXElementName::NamespacedName(ns) => {
2306 - JSXElementName::JSXNamespacedName(JSXNamespacedName {
2307 - base: self.make_base_node(ns.span),
2308 - namespace: JSXIdentifier {
2309 - base: self.make_base_node(ns.namespace.span),
2310 - name: ns.namespace.name.to_string(),
2311 - },
2312 - name: JSXIdentifier {
2313 - base: self.make_base_node(ns.name.span),
2314 - name: ns.name.name.to_string(),
2315 - },
2316 - })
2317 - }
2318 - oxc::JSXElementName::MemberExpression(mem) => {
2319 - JSXElementName::JSXMemberExpression(self.convert_jsx_member_expression(mem))
2320 - }
2321 - oxc::JSXElementName::ThisExpression(t) => {
2322 - JSXElementName::JSXIdentifier(JSXIdentifier {
2323 - base: self.make_base_node(t.span),
2324 - name: "this".to_string(),
2325 - })
2326 - }
2327 - }
2328 - }
2329 -
2330 - fn convert_jsx_member_expression(&self, mem: &oxc::JSXMemberExpression) -> JSXMemberExpression {
2331 - JSXMemberExpression {
2332 - base: self.make_base_node(mem.span),
2333 - object: Box::new(self.convert_jsx_member_expression_object(&mem.object)),
2334 - property: JSXIdentifier {
2335 - base: self.make_base_node(mem.property.span),
2336 - name: mem.property.name.to_string(),
2337 - },
2338 - }
2339 - }
2340 -
2341 - fn convert_jsx_member_expression_object(
2342 - &self,
2343 - obj: &oxc::JSXMemberExpressionObject,
2344 - ) -> JSXMemberExprObject {
2345 - match obj {
2346 - oxc::JSXMemberExpressionObject::IdentifierReference(id) => {
2347 - JSXMemberExprObject::JSXIdentifier(JSXIdentifier {
2348 - base: self.make_base_node(id.span),
2349 - name: id.name.to_string(),
2350 - })
2351 - }
2352 - oxc::JSXMemberExpressionObject::MemberExpression(mem) => {
2353 - JSXMemberExprObject::JSXMemberExpression(Box::new(
2354 - self.convert_jsx_member_expression(mem),
2355 - ))
2356 - }
2357 - oxc::JSXMemberExpressionObject::ThisExpression(t) => {
2358 - // JSX `<this.Foo />` - convert to identifier
2359 - JSXMemberExprObject::JSXIdentifier(JSXIdentifier {
2360 - base: self.make_base_node(t.span),
2361 - name: "this".to_string(),
2362 - })
2363 - }
2364 - }
2365 - }
2366 -
2367 - fn convert_jsx_attribute_item(&self, attr: &oxc::JSXAttributeItem) -> JSXAttributeItem {
2368 - match attr {
2369 - oxc::JSXAttributeItem::Attribute(a) => {
2370 - JSXAttributeItem::JSXAttribute(self.convert_jsx_attribute(a))
2371 - }
2372 - oxc::JSXAttributeItem::SpreadAttribute(s) => {
2373 - JSXAttributeItem::JSXSpreadAttribute(JSXSpreadAttribute {
2374 - base: self.make_base_node(s.span),
2375 - argument: Box::new(self.convert_expression(&s.argument)),
2376 - })
2377 - }
2378 - }
2379 - }
2380 -
2381 - fn convert_jsx_attribute(&self, attr: &oxc::JSXAttribute) -> JSXAttribute {
2382 - JSXAttribute {
2383 - base: self.make_base_node(attr.span),
2384 - name: self.convert_jsx_attribute_name(&attr.name),
2385 - value: attr
2386 - .value
2387 - .as_ref()
2388 - .map(|v| self.convert_jsx_attribute_value(v)),
2389 - }
2390 - }
2391 -
2392 - fn convert_jsx_attribute_name(&self, name: &oxc::JSXAttributeName) -> JSXAttributeName {
2393 - match name {
2394 - oxc::JSXAttributeName::Identifier(id) => {
2395 - JSXAttributeName::JSXIdentifier(JSXIdentifier {
2396 - base: self.make_base_node(id.span),
2397 - name: id.name.to_string(),
2398 - })
2399 - }
2400 - oxc::JSXAttributeName::NamespacedName(ns) => {
2401 - JSXAttributeName::JSXNamespacedName(JSXNamespacedName {
2402 - base: self.make_base_node(ns.span),
2403 - namespace: JSXIdentifier {
2404 - base: self.make_base_node(ns.namespace.span),
2405 - name: ns.namespace.name.to_string(),
2406 - },
2407 - name: JSXIdentifier {
2408 - base: self.make_base_node(ns.name.span),
2409 - name: ns.name.name.to_string(),
2410 - },
2411 - })
2412 - }
2413 - }
2414 - }
2415 -
2416 - fn convert_jsx_attribute_value(&self, value: &oxc::JSXAttributeValue) -> JSXAttributeValue {
2417 - match value {
2418 - oxc::JSXAttributeValue::StringLiteral(s) => {
2419 - JSXAttributeValue::StringLiteral(StringLiteral {
2420 - base: self.make_base_node(s.span),
2421 - value: s.value.to_string(),
2422 - })
2423 - }
2424 - oxc::JSXAttributeValue::ExpressionContainer(e) => {
2425 - JSXAttributeValue::JSXExpressionContainer(self.convert_jsx_expression_container(e))
2426 - }
2427 - oxc::JSXAttributeValue::Element(e) => {
2428 - JSXAttributeValue::JSXElement(Box::new(self.convert_jsx_element(e)))
2429 - }
2430 - oxc::JSXAttributeValue::Fragment(f) => {
2431 - JSXAttributeValue::JSXFragment(self.convert_jsx_fragment(f))
2432 - }
2433 - }
2434 - }
2435 -
2436 - fn convert_jsx_expression_container(
2437 - &self,
2438 - container: &oxc::JSXExpressionContainer,
2439 - ) -> JSXExpressionContainer {
2440 - JSXExpressionContainer {
2441 - base: self.make_base_node(container.span),
2442 - expression: match &container.expression {
2443 - oxc::JSXExpression::EmptyExpression(e) => {
2444 - JSXExpressionContainerExpr::JSXEmptyExpression(JSXEmptyExpression {
2445 - base: self.make_base_node(e.span),
2446 - })
2447 - }
2448 - other => JSXExpressionContainerExpr::Expression(Box::new(
2449 - self.convert_expression_from_jsx_expression(other),
2450 - )),
2451 - },
2452 - }
2453 - }
2454 -
2455 - fn convert_jsx_child(&self, child: &oxc::JSXChild) -> JSXChild {
2456 - match child {
2457 - oxc::JSXChild::Element(e) => {
2458 - JSXChild::JSXElement(Box::new(self.convert_jsx_element(e)))
2459 - }
2460 - oxc::JSXChild::Fragment(f) => JSXChild::JSXFragment(self.convert_jsx_fragment(f)),
2461 - oxc::JSXChild::ExpressionContainer(e) => {
2462 - JSXChild::JSXExpressionContainer(self.convert_jsx_expression_container(e))
2463 - }
2464 - oxc::JSXChild::Spread(s) => JSXChild::JSXSpreadChild(JSXSpreadChild {
2465 - base: self.make_base_node(s.span),
2466 - expression: Box::new(self.convert_expression(&s.expression)),
2467 - }),
2468 - oxc::JSXChild::Text(t) => JSXChild::JSXText(JSXText {
2469 - base: self.make_base_node(t.span),
2470 - value: t.value.to_string(),
2471 - }),
2472 - }
2473 - }
2474 -
2475 - fn convert_binding_pattern(&self, pattern: &oxc::BindingPattern) -> PatternLike {
2476 - match pattern {
2477 - oxc::BindingPattern::BindingIdentifier(id) => {
2478 - PatternLike::Identifier(self.convert_binding_identifier(id))
2479 - }
2480 - oxc::BindingPattern::ObjectPattern(obj) => {
2481 - PatternLike::ObjectPattern(self.convert_object_pattern(obj))
2482 - }
2483 - oxc::BindingPattern::ArrayPattern(arr) => {
2484 - PatternLike::ArrayPattern(self.convert_array_pattern(arr))
2485 - }
2486 - oxc::BindingPattern::AssignmentPattern(assign) => {
2487 - PatternLike::AssignmentPattern(self.convert_assignment_pattern(assign))
2488 - }
2489 - }
2490 - }
2491 -
2492 - fn convert_binding_identifier(&self, id: &oxc::BindingIdentifier) -> Identifier {
2493 - Identifier {
2494 - base: self.make_base_node(id.span),
2495 - name: id.name.to_string(),
2496 - type_annotation: None,
2497 - optional: None,
2498 - decorators: None,
2499 - }
2500 - }
2501 -
2502 - fn convert_identifier_name(&self, id: &oxc::IdentifierName) -> Identifier {
2503 - Identifier {
2504 - base: self.make_base_node(id.span),
2505 - name: id.name.to_string(),
2506 - type_annotation: None,
2507 - optional: None,
2508 - decorators: None,
2509 - }
2510 - }
2511 -
2512 - fn convert_label_identifier(&self, id: &oxc::LabelIdentifier) -> Identifier {
2513 - Identifier {
2514 - base: self.make_base_node(id.span),
2515 - name: id.name.to_string(),
2516 - type_annotation: None,
2517 - optional: None,
2518 - decorators: None,
2519 - }
2520 - }
2521 -
2522 - fn convert_identifier_reference(&self, id: &oxc::IdentifierReference) -> Identifier {
2523 - Identifier {
2524 - base: self.make_base_node(id.span),
2525 - name: id.name.to_string(),
2526 - type_annotation: None,
2527 - optional: None,
2528 - decorators: None,
2529 - }
2530 - }
2531 -
2532 - fn convert_object_pattern(&self, obj: &oxc::ObjectPattern) -> ObjectPattern {
2533 - let mut properties: Vec<ObjectPatternProperty> = obj
2534 - .properties
2535 - .iter()
2536 - .map(|p| self.convert_binding_property(p))
2537 - .collect();
2538 -
2539 - // Handle rest element (separate field in OXC v0.121)
2540 - if let Some(rest) = &obj.rest {
2541 - properties.push(ObjectPatternProperty::RestElement(
2542 - self.convert_binding_rest_element(rest),
2543 - ));
2544 - }
2545 -
2546 - ObjectPattern {
2547 - base: self.make_base_node(obj.span),
2548 - properties,
2549 - type_annotation: None,
2550 - decorators: None,
2551 - }
2552 - }
2553 -
2554 - fn convert_binding_property(&self, prop: &oxc::BindingProperty) -> ObjectPatternProperty {
2555 - // BindingProperty is now a struct (not an enum) in OXC v0.121
2556 - ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
2557 - base: self.make_base_node(prop.span),
2558 - key: Box::new(self.convert_property_key(&prop.key)),
2559 - value: Box::new(self.convert_binding_pattern(&prop.value)),
2560 - computed: prop.computed,
2561 - shorthand: prop.shorthand,
2562 - decorators: None,
2563 - method: None,
2564 - })
2565 - }
2566 -
2567 - fn convert_array_pattern(&self, arr: &oxc::ArrayPattern) -> ArrayPattern {
2568 - let mut elements: Vec<Option<PatternLike>> = arr
2569 - .elements
2570 - .iter()
2571 - .map(|e| e.as_ref().map(|p| self.convert_binding_pattern(p)))
2572 - .collect();
2573 -
2574 - // Handle rest element (separate field in OXC v0.121)
2575 - if let Some(rest) = &arr.rest {
2576 - elements.push(Some(PatternLike::RestElement(
2577 - self.convert_binding_rest_element(rest),
2578 - )));
2579 - }
2580 -
2581 - ArrayPattern {
2582 - base: self.make_base_node(arr.span),
2583 - elements,
2584 - type_annotation: None,
2585 - decorators: None,
2586 - }
2587 - }
2588 -
2589 - fn convert_assignment_pattern(&self, assign: &oxc::AssignmentPattern) -> AssignmentPattern {
2590 - AssignmentPattern {
2591 - base: self.make_base_node(assign.span),
2592 - left: Box::new(self.convert_binding_pattern(&assign.left)),
2593 - right: Box::new(self.convert_expression(&assign.right)),
2594 - type_annotation: None,
2595 - decorators: None,
2596 - }
2597 - }
2598 -
2599 - fn convert_binding_rest_element(&self, rest: &oxc::BindingRestElement) -> RestElement {
2600 - RestElement {
2601 - base: self.make_base_node(rest.span),
2602 - argument: Box::new(self.convert_binding_pattern(&rest.argument)),
2603 - type_annotation: None,
2604 - decorators: None,
2605 - }
2606 - }
2607 -
2608 - /// Convert FormalParameters (items + optional rest) to a Vec<PatternLike>.
2609 - /// OXC stores rest parameters separately from items, but Babel includes them
2610 - /// in the same params array.
2611 - fn convert_formal_parameters(&self, params: &oxc::FormalParameters) -> Vec<PatternLike> {
2612 - let mut result: Vec<PatternLike> = params
2613 - .items
2614 - .iter()
2615 - .map(|p| self.convert_formal_parameter(p))
2616 - .collect();
2617 - if let Some(rest) = &params.rest {
2618 - result.push(PatternLike::RestElement(RestElement {
2619 - base: self.make_base_node(rest.rest.span),
2620 - argument: Box::new(self.convert_binding_pattern(&rest.rest.argument)),
2621 - type_annotation: rest
2622 - .type_annotation
2623 - .as_ref()
2624 - .map(|_| RawNode::null()),
2625 - decorators: None,
2626 - }));
2627 - }
2628 - result
2629 - }
2630 -
2631 - fn convert_formal_parameter(&self, param: &oxc::FormalParameter) -> PatternLike {
2632 - let mut pattern = self.convert_binding_pattern(&param.pattern);
2633 -
2634 - // Add type annotation if present (now on FormalParameter, not BindingPattern)
2635 - if let Some(_type_annotation) = &param.type_annotation {
2636 - let type_json = RawNode::null();
2637 - match &mut pattern {
2638 - PatternLike::Identifier(id) => {
2639 - id.type_annotation = Some(type_json);
2640 - }
2641 - PatternLike::ObjectPattern(obj) => {
2642 - obj.type_annotation = Some(type_json);
2643 - }
2644 - PatternLike::ArrayPattern(arr) => {
2645 - arr.type_annotation = Some(type_json);
2646 - }
2647 - PatternLike::AssignmentPattern(assign) => {
2648 - assign.type_annotation = Some(type_json);
2649 - }
2650 - PatternLike::RestElement(rest) => {
2651 - rest.type_annotation = Some(type_json);
2652 - }
2653 - PatternLike::MemberExpression(_) => {}
2654 - PatternLike::TSAsExpression(_)
2655 - | PatternLike::TSSatisfiesExpression(_)
2656 - | PatternLike::TSNonNullExpression(_)
2657 - | PatternLike::TSTypeAssertion(_)
2658 - | PatternLike::TypeCastExpression(_) => {}
2659 - }
2660 - }
2661 -
2662 - // Handle default parameter values: OXC stores default values in
2663 - // FormalParameter.initializer rather than using BindingPattern::AssignmentPattern.
2664 - // Babel/TS expects them as AssignmentPattern in the params array.
2665 - if let Some(ref initializer) = param.initializer {
2666 - let right = self.convert_expression(initializer);
2667 - pattern = PatternLike::AssignmentPattern(AssignmentPattern {
2668 - base: self.make_base_node(param.span),
2669 - left: Box::new(pattern),
2670 - right: Box::new(right),
2671 - type_annotation: None,
2672 - decorators: None,
2673 - });
2674 - }
2675 -
2676 - pattern
2677 - }
2678 -
2679 - fn convert_function_body(&self, body: &oxc::FunctionBody) -> BlockStatement {
2680 - BlockStatement {
2681 - base: self.make_base_node(body.span),
2682 - body: body
2683 - .statements
2684 - .iter()
2685 - .map(|s| self.convert_statement(s))
2686 - .collect(),
2687 - directives: body
2688 - .directives
2689 - .iter()
2690 - .map(|d| self.convert_directive(d))
2691 - .collect(),
2692 - }
2693 - }
2694 -
2695 - // ============================================================
2696 - // Helper methods for converting Expression-inheriting enums
2697 - // These handle the case where OXC enums inherit from Expression
2698 - // via @inherit and each variant has a differently-typed Box.
2699 - // ============================================================
2700 -
2701 - /// Convert Argument expression variants (not SpreadElement) to Expression
2702 - fn convert_expression_from_argument(&self, arg: &oxc::Argument) -> Expression {
2703 - self.convert_expression_like(arg)
2704 - }
2705 -
2706 - /// Convert ArrayExpressionElement expression variants to Expression
2707 - fn convert_expression_from_array_element(
2708 - &self,
2709 - elem: &oxc::ArrayExpressionElement,
2710 - ) -> Expression {
2711 - self.convert_expression_like(elem)
2712 - }
2713 -
2714 - /// Convert ExportDefaultDeclarationKind expression variants to Expression
2715 - fn convert_expression_from_export_default(
2716 - &self,
2717 - kind: &oxc::ExportDefaultDeclarationKind,
2718 - ) -> Expression {
2719 - self.convert_expression_like(kind)
2720 - }
2721 -
2722 - /// Convert PropertyKey expression variants to Expression
2723 - fn convert_expression_from_property_key(&self, key: &oxc::PropertyKey) -> Expression {
2724 - self.convert_expression_like(key)
2725 - }
2726 -
2727 - /// Convert JSXExpression expression variants to Expression
2728 - fn convert_expression_from_jsx_expression(&self, expr: &oxc::JSXExpression) -> Expression {
2729 - self.convert_expression_like(expr)
2730 - }
2731 -
2732 - /// Generic helper to convert any enum that inherits Expression variants.
2733 - /// Uses the ExpressionLike trait.
2734 - fn convert_expression_like<T: ExpressionLike>(&self, value: &T) -> Expression {
2735 - value.convert_with(self)
2736 - }
2737 -}
2738 -
2739 -/// Trait for enums that inherit Expression variants.
2740 -/// Each implementing type matches its Expression-inherited variants and
2741 -/// delegates to ConvertCtx::convert_expression by constructing the equivalent
2742 -/// Expression variant.
2743 -trait ExpressionLike {
2744 - fn convert_with(&self, ctx: &ConvertCtx) -> Expression;
2745 -}
2746 -
2747 -/// Macro to implement ExpressionLike for enums that @inherit Expression.
2748 -/// Each variant name matches the Expression variant name, so we can
2749 -/// deref the inner Box and call the appropriate convert method.
2750 -macro_rules! impl_expression_like {
2751 - ($enum_ty:ty, [$($non_expr_variant:pat => $non_expr_handler:expr),*]) => {
2752 - impl<'a> ExpressionLike for $enum_ty {
2753 - fn convert_with(&self, ctx: &ConvertCtx) -> Expression {
2754 - match self {
2755 - $($non_expr_variant => $non_expr_handler,)*
2756 - // Expression-inherited variants
2757 - Self::BooleanLiteral(e) => Expression::BooleanLiteral(BooleanLiteral {
2758 - base: ctx.make_base_node(e.span),
2759 - value: e.value,
2760 - }),
2761 - Self::NullLiteral(e) => Expression::NullLiteral(NullLiteral {
2762 - base: ctx.make_base_node(e.span),
2763 - }),
2764 - Self::NumericLiteral(e) => Expression::NumericLiteral(NumericLiteral {
2765 - base: ctx.make_base_node(e.span),
2766 - value: e.value,
2767 - extra: None,
2768 - }),
2769 - Self::BigIntLiteral(e) => Expression::BigIntLiteral(BigIntLiteral {
2770 - base: ctx.make_base_node(e.span),
2771 - value: e.raw.as_ref().map(|r| r.to_string()).unwrap_or_default(),
2772 - }),
2773 - Self::RegExpLiteral(e) => Expression::RegExpLiteral(RegExpLiteral {
2774 - base: ctx.make_base_node(e.span),
2775 - pattern: e.regex.pattern.text.to_string(),
2776 - flags: e.regex.flags.to_string(),
2777 - }),
2778 - Self::StringLiteral(e) => Expression::StringLiteral(StringLiteral {
2779 - base: ctx.make_base_node(e.span),
2780 - value: e.value.to_string(),
2781 - }),
2782 - Self::TemplateLiteral(e) => Expression::TemplateLiteral(ctx.convert_template_literal(e)),
2783 - Self::Identifier(e) => Expression::Identifier(ctx.convert_identifier_reference(e)),
2784 - Self::MetaProperty(e) => Expression::MetaProperty(ctx.convert_meta_property(e)),
2785 - Self::Super(e) => Expression::Super(Super { base: ctx.make_base_node(e.span) }),
2786 - Self::ArrayExpression(e) => Expression::ArrayExpression(ctx.convert_array_expression(e)),
2787 - Self::ArrowFunctionExpression(e) => Expression::ArrowFunctionExpression(ctx.convert_arrow_function_expression(e)),
2788 - Self::AssignmentExpression(e) => Expression::AssignmentExpression(ctx.convert_assignment_expression(e)),
2789 - Self::AwaitExpression(e) => Expression::AwaitExpression(ctx.convert_await_expression(e)),
2790 - Self::BinaryExpression(e) => Expression::BinaryExpression(ctx.convert_binary_expression(e)),
2791 - Self::CallExpression(e) => Expression::CallExpression(ctx.convert_call_expression(e)),
2792 - Self::ChainExpression(e) => ctx.convert_chain_expression(e),
2793 - Self::ClassExpression(e) => Expression::ClassExpression(ctx.convert_class_expression(e)),
2794 - Self::ConditionalExpression(e) => Expression::ConditionalExpression(ctx.convert_conditional_expression(e)),
2795 - Self::FunctionExpression(e) => Expression::FunctionExpression(ctx.convert_function_expression(e)),
2796 - Self::ImportExpression(i) => {
2797 - let mut args = vec![ctx.convert_expression(&i.source)];
2798 - if let Some(ref opts) = i.options {
2799 - args.push(ctx.convert_expression(opts));
2800 - }
2801 - Expression::CallExpression(CallExpression {
2802 - base: ctx.make_base_node(i.span),
2803 - callee: Box::new(Expression::Import(Import {
2804 - base: ctx.make_base_node(i.span),
2805 - })),
2806 - arguments: args,
2807 - optional: None,
2808 - type_arguments: None,
2809 - type_parameters: None,
2810 - })
2811 - }
2812 - Self::LogicalExpression(e) => Expression::LogicalExpression(ctx.convert_logical_expression(e)),
2813 - Self::NewExpression(e) => Expression::NewExpression(ctx.convert_new_expression(e)),
2814 - Self::ObjectExpression(e) => Expression::ObjectExpression(ctx.convert_object_expression(e)),
2815 - Self::ParenthesizedExpression(e) => Expression::ParenthesizedExpression(ctx.convert_parenthesized_expression(e)),
2816 - Self::SequenceExpression(e) => Expression::SequenceExpression(ctx.convert_sequence_expression(e)),
2817 - Self::TaggedTemplateExpression(e) => Expression::TaggedTemplateExpression(ctx.convert_tagged_template_expression(e)),
2818 - Self::ThisExpression(e) => Expression::ThisExpression(ThisExpression { base: ctx.make_base_node(e.span) }),
2819 - Self::UnaryExpression(e) => Expression::UnaryExpression(ctx.convert_unary_expression(e)),
2820 - Self::UpdateExpression(e) => Expression::UpdateExpression(ctx.convert_update_expression(e)),
2821 - Self::YieldExpression(e) => Expression::YieldExpression(ctx.convert_yield_expression(e)),
2822 - Self::PrivateInExpression(_) => todo!("PrivateInExpression"),
2823 - Self::JSXElement(e) => Expression::JSXElement(Box::new(ctx.convert_jsx_element(e))),
2824 - Self::JSXFragment(e) => Expression::JSXFragment(ctx.convert_jsx_fragment(e)),
2825 - Self::TSAsExpression(e) => Expression::TSAsExpression(ctx.convert_ts_as_expression(e)),
2826 - Self::TSSatisfiesExpression(e) => Expression::TSSatisfiesExpression(ctx.convert_ts_satisfies_expression(e)),
2827 - Self::TSTypeAssertion(e) => Expression::TSTypeAssertion(ctx.convert_ts_type_assertion(e)),
2828 - Self::TSNonNullExpression(e) => Expression::TSNonNullExpression(ctx.convert_ts_non_null_expression(e)),
2829 - Self::TSInstantiationExpression(e) => Expression::TSInstantiationExpression(ctx.convert_ts_instantiation_expression(e)),
2830 - Self::ComputedMemberExpression(e) => Expression::MemberExpression(MemberExpression {
2831 - base: ctx.make_base_node(e.span),
2832 - object: Box::new(ctx.convert_expression(&e.object)),
2833 - property: Box::new(ctx.convert_expression(&e.expression)),
2834 - computed: true,
2835 - }),
2836 - Self::StaticMemberExpression(e) => Expression::MemberExpression(MemberExpression {
2837 - base: ctx.make_base_node(e.span),
2838 - object: Box::new(ctx.convert_expression(&e.object)),
2839 - property: Box::new(Expression::Identifier(ctx.convert_identifier_name(&e.property))),
2840 - computed: false,
2841 - }),
2842 - Self::PrivateFieldExpression(e) => Expression::MemberExpression(MemberExpression {
2843 - base: ctx.make_base_node(e.span),
2844 - object: Box::new(ctx.convert_expression(&e.object)),
2845 - property: Box::new(Expression::PrivateName(PrivateName {
2846 - base: ctx.make_base_node(e.field.span),
2847 - id: Identifier {
2848 - base: ctx.make_base_node(e.field.span),
2849 - name: e.field.name.to_string(),
2850 - type_annotation: None,
2851 - optional: None,
2852 - decorators: None,
2853 - },
2854 - })),
2855 - computed: false,
2856 - }),
2857 - Self::V8IntrinsicExpression(_) => todo!("V8IntrinsicExpression"),
2858 - }
2859 - }
2860 - }
2861 - };
2862 -}
2863 -
2864 -// ForStatementInit: VariableDeclaration + @inherit Expression
2865 -impl_expression_like!(oxc::ForStatementInit<'a>, [
2866 - Self::VariableDeclaration(_) => unreachable!("handled separately")
2867 -]);
2868 -
2869 -// Argument: SpreadElement + @inherit Expression
2870 -impl_expression_like!(oxc::Argument<'a>, [
2871 - Self::SpreadElement(_) => unreachable!("handled separately")
2872 -]);
2873 -
2874 -// ArrayExpressionElement: SpreadElement + Elision + @inherit Expression
2875 -impl_expression_like!(oxc::ArrayExpressionElement<'a>, [
2876 - Self::SpreadElement(_) => unreachable!("handled separately"),
2877 - Self::Elision(_) => unreachable!("handled separately")
2878 -]);
2879 -
2880 -// ExportDefaultDeclarationKind: FunctionDeclaration + ClassDeclaration + TSInterfaceDeclaration + @inherit Expression
2881 -impl_expression_like!(oxc::ExportDefaultDeclarationKind<'a>, [
2882 - Self::FunctionDeclaration(_) => unreachable!("handled separately"),
2883 - Self::ClassDeclaration(_) => unreachable!("handled separately"),
2884 - Self::TSInterfaceDeclaration(_) => unreachable!("handled separately")
2885 -]);
2886 -
2887 -// PropertyKey: StaticIdentifier + PrivateIdentifier + @inherit Expression
2888 -impl_expression_like!(oxc::PropertyKey<'a>, [
2889 - Self::StaticIdentifier(_) => unreachable!("handled separately"),
2890 - Self::PrivateIdentifier(_) => unreachable!("handled separately")
2891 -]);
2892 -
2893 -// JSXExpression: EmptyExpression + @inherit Expression
2894 -impl_expression_like!(oxc::JSXExpression<'a>, [
2895 - Self::EmptyExpression(_) => unreachable!("handled separately")
2896 -]);
compiler/crates/react_compiler_oxc/src/convert_ast_reverse.rs deleted
-1942
@@ -1,1942 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -//! Reverse AST converter: react_compiler_ast (Babel format) → OXC AST.
7 -//!
8 -//! This is the inverse of `convert_ast.rs`. It takes a `react_compiler_ast::File`
9 -//! (which represents the compiler's Babel-compatible output) and produces OXC AST
10 -//! nodes allocated in an OXC arena, suitable for code generation via `oxc_codegen`.
11 -
12 -use oxc_allocator::Allocator;
13 -use oxc_allocator::FromIn;
14 -use oxc_ast::ast as oxc;
15 -use oxc_span::Atom;
16 -use oxc_span::SPAN;
17 -use oxc_span::Span;
18 -use react_compiler_ast::common::BaseNode;
19 -use react_compiler_ast::declarations::*;
20 -use react_compiler_ast::expressions::*;
21 -use react_compiler_ast::jsx::*;
22 -use react_compiler_ast::operators::*;
23 -use react_compiler_ast::patterns::*;
24 -use react_compiler_ast::statements::*;
25 -
26 -/// Set the span on an OXC Statement.
27 -fn set_statement_span(stmt: &mut oxc::Statement<'_>, span: Span) {
28 - use oxc_span::GetSpanMut;
29 - match stmt {
30 - oxc::Statement::ImportDeclaration(d) => *d.span_mut() = span,
31 - oxc::Statement::VariableDeclaration(d) => *d.span_mut() = span,
32 - oxc::Statement::FunctionDeclaration(d) => *d.span_mut() = span,
33 - oxc::Statement::ExportNamedDeclaration(d) => *d.span_mut() = span,
34 - oxc::Statement::ExportDefaultDeclaration(d) => *d.span_mut() = span,
35 - oxc::Statement::ExportAllDeclaration(d) => *d.span_mut() = span,
36 - oxc::Statement::ExpressionStatement(s) => *s.span_mut() = span,
37 - oxc::Statement::IfStatement(s) => *s.span_mut() = span,
38 - oxc::Statement::ForStatement(s) => *s.span_mut() = span,
39 - oxc::Statement::WhileStatement(s) => *s.span_mut() = span,
40 - oxc::Statement::DoWhileStatement(s) => *s.span_mut() = span,
41 - oxc::Statement::ForInStatement(s) => *s.span_mut() = span,
42 - oxc::Statement::ForOfStatement(s) => *s.span_mut() = span,
43 - oxc::Statement::SwitchStatement(s) => *s.span_mut() = span,
44 - oxc::Statement::ThrowStatement(s) => *s.span_mut() = span,
45 - oxc::Statement::TryStatement(s) => *s.span_mut() = span,
46 - oxc::Statement::BreakStatement(s) => *s.span_mut() = span,
47 - oxc::Statement::ContinueStatement(s) => *s.span_mut() = span,
48 - oxc::Statement::LabeledStatement(s) => *s.span_mut() = span,
49 - oxc::Statement::BlockStatement(s) => *s.span_mut() = span,
50 - oxc::Statement::ReturnStatement(s) => *s.span_mut() = span,
51 - oxc::Statement::WithStatement(s) => *s.span_mut() = span,
52 - oxc::Statement::EmptyStatement(s) => *s.span_mut() = span,
53 - oxc::Statement::DebuggerStatement(s) => *s.span_mut() = span,
54 - _ => {} // ClassDeclaration etc. - leave as-is
55 - }
56 -}
57 -
58 -/// Convert a `react_compiler_ast::File` into an OXC `Program` allocated in the given arena.
59 -pub fn convert_program_to_oxc<'a>(
60 - file: &react_compiler_ast::File,
61 - allocator: &'a Allocator,
62 -) -> oxc::Program<'a> {
63 - let ctx = ReverseCtx::new(allocator, None);
64 - ctx.convert_program(&file.program)
65 -}
66 -
67 -/// Convert with source text available for extracting TS declarations.
68 -pub fn convert_program_to_oxc_with_source<'a>(
69 - file: &react_compiler_ast::File,
70 - allocator: &'a Allocator,
71 - source_text: &str,
72 -) -> oxc::Program<'a> {
73 - let ctx = ReverseCtx::new(allocator, Some(source_text.to_string()));
74 - ctx.convert_program(&file.program)
75 -}
76 -
77 -struct ReverseCtx<'a> {
78 - allocator: &'a Allocator,
79 - builder: oxc_ast::AstBuilder<'a>,
80 - source_text: Option<String>,
81 -}
82 -
83 -impl<'a> ReverseCtx<'a> {
84 - fn new(allocator: &'a Allocator, source_text: Option<String>) -> Self {
85 - Self {
86 - allocator,
87 - builder: oxc_ast::AstBuilder::new(allocator),
88 - source_text,
89 - }
90 - }
91 -
92 - /// Extract a statement from the original source text using the base node's
93 - /// start/end positions. Re-parses the snippet with OXC to get a proper AST node.
94 - fn extract_source_stmt(&self, base: &BaseNode) -> Option<oxc::Statement<'a>> {
95 - let source = self.source_text.as_deref()?;
96 - let start = base.start? as usize;
97 - let end = base.end? as usize;
98 - if start >= source.len() || end > source.len() || start >= end {
99 - return None;
100 - }
101 - let text = &source[start..end];
102 - // Copy the text into the allocator so its lifetime matches 'a
103 - let text_in_alloc = oxc_allocator::StringBuilder::from_str_in(text, self.allocator);
104 - let text_ref: &'a str = text_in_alloc.into_str();
105 - let source_type = oxc_span::SourceType::tsx();
106 - let parsed = oxc_parser::Parser::new(self.allocator, text_ref, source_type).parse();
107 - if parsed.panicked || parsed.program.body.is_empty() {
108 - return None;
109 - }
110 - let stmt = parsed.program.body.into_iter().next()?;
111 - Some(stmt)
112 - }
113 -
114 - /// Allocate a string in the arena and return an Atom with lifetime 'a.
115 - fn atom(&self, s: &str) -> Atom<'a> {
116 - Atom::from_in(s, self.allocator)
117 - }
118 -
119 - /// Convert a BaseNode's start/end into an OXC Span.
120 - /// Returns SPAN (0,0) if the base has no position info.
121 - fn span_from_base(&self, base: &BaseNode) -> Span {
122 - match (base.start, base.end) {
123 - (Some(start), Some(end)) => Span::new(start, end),
124 - (Some(start), None) => Span::new(start, start),
125 - _ => SPAN,
126 - }
127 - }
128 -
129 - // ===== Program =====
130 -
131 - fn convert_program(&self, program: &react_compiler_ast::Program) -> oxc::Program<'a> {
132 - let source_type = match program.source_type {
133 - react_compiler_ast::SourceType::Module => oxc_span::SourceType::mjs(),
134 - react_compiler_ast::SourceType::Script => oxc_span::SourceType::cjs(),
135 - };
136 -
137 - // Use convert_statements_with_spans for the top-level body so that
138 - // original source positions are preserved. This allows comments from
139 - // the original source to be correctly attached to statements.
140 - let body = self.convert_statements_with_spans(&program.body);
141 - let directives = self.convert_directives(&program.directives);
142 - let comments = self.builder.vec();
143 -
144 - self.builder.program(
145 - SPAN,
146 - source_type,
147 - "",
148 - comments,
149 - None, // hashbang
150 - directives,
151 - body,
152 - )
153 - }
154 -
155 - // ===== Directives =====
156 -
157 - fn convert_directives(
158 - &self,
159 - directives: &[Directive],
160 - ) -> oxc_allocator::Vec<'a, oxc::Directive<'a>> {
161 - self.builder
162 - .vec_from_iter(directives.iter().map(|d| self.convert_directive(d)))
163 - }
164 -
165 - fn convert_directive(&self, d: &Directive) -> oxc::Directive<'a> {
166 - let expression = self
167 - .builder
168 - .string_literal(SPAN, self.atom(&d.value.value), None);
169 - self.builder
170 - .directive(SPAN, expression, self.atom(&d.value.value))
171 - }
172 -
173 - // ===== Statements =====
174 -
175 - /// Convert statements preserving span info from the Babel AST.
176 - /// This is used for top-level program body where span positions
177 - /// are needed for comment attachment.
178 - fn convert_statements_with_spans(
179 - &self,
180 - stmts: &[Statement],
181 - ) -> oxc_allocator::Vec<'a, oxc::Statement<'a>> {
182 - self.builder.vec_from_iter(stmts.iter().map(|s| {
183 - let span = self.get_statement_span(s);
184 - let mut oxc_stmt = self.convert_statement(s);
185 - if span != SPAN {
186 - set_statement_span(&mut oxc_stmt, span);
187 - }
188 - oxc_stmt
189 - }))
190 - }
191 -
192 - /// Extract the span from a Babel AST Statement's base node.
193 - fn get_statement_span(&self, stmt: &Statement) -> Span {
194 - let base = match stmt {
195 - Statement::BlockStatement(s) => &s.base,
196 - Statement::ReturnStatement(s) => &s.base,
197 - Statement::ExpressionStatement(s) => &s.base,
198 - Statement::IfStatement(s) => &s.base,
199 - Statement::ForStatement(s) => &s.base,
200 - Statement::WhileStatement(s) => &s.base,
201 - Statement::DoWhileStatement(s) => &s.base,
202 - Statement::ForInStatement(s) => &s.base,
203 - Statement::ForOfStatement(s) => &s.base,
204 - Statement::SwitchStatement(s) => &s.base,
205 - Statement::ThrowStatement(s) => &s.base,
206 - Statement::TryStatement(s) => &s.base,
207 - Statement::BreakStatement(s) => &s.base,
208 - Statement::ContinueStatement(s) => &s.base,
209 - Statement::LabeledStatement(s) => &s.base,
210 - Statement::EmptyStatement(s) => &s.base,
211 - Statement::DebuggerStatement(s) => &s.base,
212 - Statement::WithStatement(s) => &s.base,
213 - Statement::VariableDeclaration(d) => &d.base,
214 - Statement::FunctionDeclaration(f) => &f.base,
215 - Statement::ClassDeclaration(c) => &c.base,
216 - Statement::ImportDeclaration(d) => &d.base,
217 - Statement::ExportNamedDeclaration(d) => &d.base,
218 - Statement::ExportDefaultDeclaration(d) => &d.base,
219 - Statement::ExportAllDeclaration(d) => &d.base,
220 - _ => return SPAN,
221 - };
222 - self.span_from_base(base)
223 - }
224 -
225 - fn convert_statement(&self, stmt: &Statement) -> oxc::Statement<'a> {
226 - match stmt {
227 - Statement::BlockStatement(s) => self
228 - .builder
229 - .statement_block(SPAN, self.convert_statement_vec(&s.body)),
230 - Statement::ReturnStatement(s) => self.builder.statement_return(
231 - SPAN,
232 - s.argument.as_ref().map(|a| self.convert_expression(a)),
233 - ),
234 - Statement::ExpressionStatement(s) => self
235 - .builder
236 - .statement_expression(SPAN, self.convert_expression(&s.expression)),
237 - Statement::IfStatement(s) => self.builder.statement_if(
238 - SPAN,
239 - self.convert_expression(&s.test),
240 - self.convert_statement(&s.consequent),
241 - s.alternate.as_ref().map(|a| self.convert_statement(a)),
242 - ),
243 - Statement::ForStatement(s) => {
244 - let init = s.init.as_ref().map(|i| self.convert_for_init(i));
245 - let test = s.test.as_ref().map(|t| self.convert_expression(t));
246 - let update = s.update.as_ref().map(|u| self.convert_expression(u));
247 - let body = self.convert_statement(&s.body);
248 - self.builder.statement_for(SPAN, init, test, update, body)
249 - }
250 - Statement::WhileStatement(s) => self.builder.statement_while(
251 - SPAN,
252 - self.convert_expression(&s.test),
253 - self.convert_statement(&s.body),
254 - ),
255 - Statement::DoWhileStatement(s) => self.builder.statement_do_while(
256 - SPAN,
257 - self.convert_statement(&s.body),
258 - self.convert_expression(&s.test),
259 - ),
260 - Statement::ForInStatement(s) => self.builder.statement_for_in(
261 - SPAN,
262 - self.convert_for_in_of_left(&s.left),
263 - self.convert_expression(&s.right),
264 - self.convert_statement(&s.body),
265 - ),
266 - Statement::ForOfStatement(s) => self.builder.statement_for_of(
267 - SPAN,
268 - s.is_await,
269 - self.convert_for_in_of_left(&s.left),
270 - self.convert_expression(&s.right),
271 - self.convert_statement(&s.body),
272 - ),
273 - Statement::SwitchStatement(s) => {
274 - let cases = self.builder.vec_from_iter(s.cases.iter().map(|c| {
275 - self.builder.switch_case(
276 - SPAN,
277 - c.test.as_ref().map(|t| self.convert_expression(t)),
278 - self.convert_statement_vec(&c.consequent),
279 - )
280 - }));
281 - self.builder
282 - .statement_switch(SPAN, self.convert_expression(&s.discriminant), cases)
283 - }
284 - Statement::ThrowStatement(s) => self
285 - .builder
286 - .statement_throw(SPAN, self.convert_expression(&s.argument)),
287 - Statement::TryStatement(s) => {
288 - let block = self.convert_block_statement(&s.block);
289 - let handler = s.handler.as_ref().map(|h| self.convert_catch_clause(h));
290 - let finalizer = s
291 - .finalizer
292 - .as_ref()
293 - .map(|f| self.convert_block_statement(f));
294 - self.builder.statement_try(SPAN, block, handler, finalizer)
295 - }
296 - Statement::BreakStatement(s) => {
297 - let label = s
298 - .label
299 - .as_ref()
300 - .map(|l| self.builder.label_identifier(SPAN, self.atom(&l.name)));
301 - self.builder.statement_break(SPAN, label)
302 - }
303 - Statement::ContinueStatement(s) => {
304 - let label = s
305 - .label
306 - .as_ref()
307 - .map(|l| self.builder.label_identifier(SPAN, self.atom(&l.name)));
308 - self.builder.statement_continue(SPAN, label)
309 - }
310 - Statement::LabeledStatement(s) => {
311 - let label = self
312 - .builder
313 - .label_identifier(SPAN, self.atom(&s.label.name));
314 - self.builder
315 - .statement_labeled(SPAN, label, self.convert_statement(&s.body))
316 - }
317 - Statement::EmptyStatement(_) => self.builder.statement_empty(SPAN),
318 - Statement::DebuggerStatement(_) => self.builder.statement_debugger(SPAN),
319 - Statement::WithStatement(s) => self.builder.statement_with(
320 - SPAN,
321 - self.convert_expression(&s.object),
322 - self.convert_statement(&s.body),
323 - ),
324 - Statement::VariableDeclaration(d) => {
325 - let decl = self.convert_variable_declaration(d);
326 - oxc::Statement::VariableDeclaration(self.builder.alloc(decl))
327 - }
328 - Statement::FunctionDeclaration(f) => {
329 - let func = self.convert_function_decl(f, oxc::FunctionType::FunctionDeclaration);
330 - oxc::Statement::FunctionDeclaration(self.builder.alloc(func))
331 - }
332 - Statement::ClassDeclaration(c) => {
333 - let class = self.convert_class_declaration(c);
334 - oxc::Statement::ClassDeclaration(self.builder.alloc(class))
335 - }
336 - Statement::ImportDeclaration(d) => {
337 - let decl = self.convert_import_declaration(d);
338 - oxc::Statement::ImportDeclaration(self.builder.alloc(decl))
339 - }
340 - Statement::ExportNamedDeclaration(d) => {
341 - let decl = self.convert_export_named_declaration(d);
342 - oxc::Statement::ExportNamedDeclaration(self.builder.alloc(decl))
343 - }
344 - Statement::ExportDefaultDeclaration(d) => {
345 - let decl = self.convert_export_default_declaration(d);
346 - oxc::Statement::ExportDefaultDeclaration(self.builder.alloc(decl))
347 - }
348 - Statement::ExportAllDeclaration(d) => {
349 - let decl = self.convert_export_all_declaration(d);
350 - oxc::Statement::ExportAllDeclaration(self.builder.alloc(decl))
351 - }
352 - // TS/Flow declarations - try to extract from source text, fall back to empty
353 - Statement::TSTypeAliasDeclaration(d) => self
354 - .extract_source_stmt(&d.base)
355 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
356 - Statement::TSInterfaceDeclaration(d) => self
357 - .extract_source_stmt(&d.base)
358 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
359 - Statement::TSEnumDeclaration(d) => self
360 - .extract_source_stmt(&d.base)
361 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
362 - Statement::TSModuleDeclaration(d) => self
363 - .extract_source_stmt(&d.base)
364 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
365 - Statement::TSDeclareFunction(d) => self
366 - .extract_source_stmt(&d.base)
367 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
368 - Statement::TypeAlias(d) => self
369 - .extract_source_stmt(&d.base)
370 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
371 - Statement::OpaqueType(d) => self
372 - .extract_source_stmt(&d.base)
373 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
374 - Statement::InterfaceDeclaration(d) => self
375 - .extract_source_stmt(&d.base)
376 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
377 - Statement::EnumDeclaration(d) => self
378 - .extract_source_stmt(&d.base)
379 - .unwrap_or_else(|| self.builder.statement_empty(SPAN)),
380 - Statement::DeclareVariable(_)
381 - | Statement::DeclareFunction(_)
382 - | Statement::DeclareClass(_)
383 - | Statement::DeclareModule(_)
384 - | Statement::DeclareModuleExports(_)
385 - | Statement::DeclareExportDeclaration(_)
386 - | Statement::DeclareExportAllDeclaration(_)
387 - | Statement::DeclareInterface(_)
388 - | Statement::DeclareTypeAlias(_)
389 - | Statement::DeclareOpaqueType(_) => self.builder.statement_empty(SPAN),
390 - // The OXC forward converter never produces `Unknown` today.
391 - // Degrading to an empty statement would silently drop the node,
392 - // so emit a deliberate runtime tripwire (a `throw` in generated
393 - // code) if this arm is ever reached.
394 - Statement::Unknown(unknown) => {
395 - let message = format!(
396 - "[react-compiler] internal error: unmodeled statement `{}` reached the OXC reverse converter",
397 - unknown.node_type()
398 - );
399 - let arg = self
400 - .builder
401 - .expression_string_literal(SPAN, self.atom(&message), None);
402 - self.builder.statement_throw(SPAN, arg)
403 - }
404 - }
405 - }
406 -
407 - fn convert_statement_vec(
408 - &self,
409 - stmts: &[Statement],
410 - ) -> oxc_allocator::Vec<'a, oxc::Statement<'a>> {
411 - self.builder
412 - .vec_from_iter(stmts.iter().map(|s| self.convert_statement(s)))
413 - }
414 -
415 - fn convert_block_statement(&self, block: &BlockStatement) -> oxc::BlockStatement<'a> {
416 - self.builder
417 - .block_statement(SPAN, self.convert_statement_vec(&block.body))
418 - }
419 -
420 - fn convert_catch_clause(&self, clause: &CatchClause) -> oxc::CatchClause<'a> {
421 - let param = clause.param.as_ref().map(|p| {
422 - let pattern = self.convert_pattern_to_binding_pattern(p);
423 - self.builder.catch_parameter(
424 - SPAN,
425 - pattern,
426 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
427 - )
428 - });
429 - self.builder
430 - .catch_clause(SPAN, param, self.convert_block_statement(&clause.body))
431 - }
432 -
433 - fn convert_for_init(&self, init: &ForInit) -> oxc::ForStatementInit<'a> {
434 - match init {
435 - ForInit::VariableDeclaration(v) => {
436 - let decl = self.convert_variable_declaration(v);
437 - oxc::ForStatementInit::VariableDeclaration(self.builder.alloc(decl))
438 - }
439 - ForInit::Expression(e) => oxc::ForStatementInit::from(self.convert_expression(e)),
440 - }
441 - }
442 -
443 - fn convert_for_in_of_left(&self, left: &ForInOfLeft) -> oxc::ForStatementLeft<'a> {
444 - match left {
445 - ForInOfLeft::VariableDeclaration(v) => {
446 - let decl = self.convert_variable_declaration(v);
447 - oxc::ForStatementLeft::VariableDeclaration(self.builder.alloc(decl))
448 - }
449 - ForInOfLeft::Pattern(p) => {
450 - let target = self.convert_pattern_to_assignment_target(p);
451 - oxc::ForStatementLeft::from(target)
452 - }
453 - }
454 - }
455 -
456 - fn convert_variable_declaration(
457 - &self,
458 - decl: &VariableDeclaration,
459 - ) -> oxc::VariableDeclaration<'a> {
460 - let kind = match decl.kind {
461 - VariableDeclarationKind::Var => oxc::VariableDeclarationKind::Var,
462 - VariableDeclarationKind::Let => oxc::VariableDeclarationKind::Let,
463 - VariableDeclarationKind::Const => oxc::VariableDeclarationKind::Const,
464 - VariableDeclarationKind::Using => oxc::VariableDeclarationKind::Using,
465 - };
466 - let declarators = self.builder.vec_from_iter(
467 - decl.declarations
468 - .iter()
469 - .map(|d| self.convert_variable_declarator(d, kind)),
470 - );
471 - let declare = decl.declare.unwrap_or(false);
472 - self.builder
473 - .variable_declaration(SPAN, kind, declarators, declare)
474 - }
475 -
476 - fn convert_variable_declarator(
477 - &self,
478 - d: &VariableDeclarator,
479 - kind: oxc::VariableDeclarationKind,
480 - ) -> oxc::VariableDeclarator<'a> {
481 - let id = self.convert_pattern_to_binding_pattern(&d.id);
482 - let init = d.init.as_ref().map(|e| self.convert_expression(e));
483 - let definite = d.definite.unwrap_or(false);
484 - self.builder.variable_declarator(
485 - SPAN,
486 - kind,
487 - id,
488 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
489 - init,
490 - definite,
491 - )
492 - }
493 -
494 - // ===== Expressions =====
495 -
496 - fn convert_expression(&self, expr: &Expression) -> oxc::Expression<'a> {
497 - match expr {
498 - Expression::Identifier(id) => self
499 - .builder
500 - .expression_identifier(SPAN, self.atom(&id.name)),
501 - Expression::StringLiteral(lit) => {
502 - self.builder
503 - .expression_string_literal(SPAN, self.atom(&lit.value), None)
504 - }
505 - Expression::NumericLiteral(lit) => self.builder.expression_numeric_literal(
506 - SPAN,
507 - lit.value,
508 - None,
509 - oxc::NumberBase::Decimal,
510 - ),
511 - Expression::BooleanLiteral(lit) => {
512 - self.builder.expression_boolean_literal(SPAN, lit.value)
513 - }
514 - Expression::NullLiteral(_) => self.builder.expression_null_literal(SPAN),
515 - Expression::BigIntLiteral(lit) => self.builder.expression_big_int_literal(
516 - SPAN,
517 - self.atom(&lit.value),
518 - None,
519 - oxc::BigintBase::Decimal,
520 - ),
521 - Expression::RegExpLiteral(lit) => {
522 - let flags = self.parse_regexp_flags(&lit.flags);
523 - let pattern = oxc::RegExpPattern {
524 - text: self.atom(&lit.pattern),
525 - pattern: None,
526 - };
527 - let regex = oxc::RegExp { pattern, flags };
528 - self.builder.expression_reg_exp_literal(SPAN, regex, None)
529 - }
530 - Expression::CallExpression(call) => {
531 - let callee = self.convert_expression(&call.callee);
532 - let args = self.convert_arguments(&call.arguments);
533 - self.builder.expression_call(
534 - SPAN,
535 - callee,
536 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
537 - args,
538 - false,
539 - )
540 - }
541 - Expression::MemberExpression(m) => self.convert_member_expression(m),
542 - Expression::OptionalCallExpression(call) => {
543 - let callee = self.convert_expression_for_chain(&call.callee);
544 - let args = self.convert_arguments(&call.arguments);
545 - let chain_call = self.builder.chain_element_call_expression(
546 - SPAN,
547 - callee,
548 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
549 - args,
550 - call.optional,
551 - );
552 - self.builder.expression_chain(SPAN, chain_call)
553 - }
554 - Expression::OptionalMemberExpression(m) => {
555 - let chain_elem = self.convert_optional_member_to_chain_element(m);
556 - self.builder.expression_chain(SPAN, chain_elem)
557 - }
558 - Expression::BinaryExpression(bin) => {
559 - let op = self.convert_binary_operator(&bin.operator);
560 - self.builder.expression_binary(
561 - SPAN,
562 - self.convert_expression(&bin.left),
563 - op,
564 - self.convert_expression(&bin.right),
565 - )
566 - }
567 - Expression::LogicalExpression(log) => {
568 - let op = self.convert_logical_operator(&log.operator);
569 - self.builder.expression_logical(
570 - SPAN,
571 - self.convert_expression(&log.left),
572 - op,
573 - self.convert_expression(&log.right),
574 - )
575 - }
576 - Expression::UnaryExpression(un) => {
577 - let op = self.convert_unary_operator(&un.operator);
578 - self.builder
579 - .expression_unary(SPAN, op, self.convert_expression(&un.argument))
580 - }
581 - Expression::UpdateExpression(up) => {
582 - let op = self.convert_update_operator(&up.operator);
583 - let arg = self.convert_expression_to_simple_assignment_target(&up.argument);
584 - self.builder.expression_update(SPAN, op, up.prefix, arg)
585 - }
586 - Expression::ConditionalExpression(cond) => self.builder.expression_conditional(
587 - SPAN,
588 - self.convert_expression(&cond.test),
589 - self.convert_expression(&cond.consequent),
590 - self.convert_expression(&cond.alternate),
591 - ),
592 - Expression::AssignmentExpression(assign) => {
593 - let op = self.convert_assignment_operator(&assign.operator);
594 - let left = self.convert_pattern_to_assignment_target(&assign.left);
595 - self.builder.expression_assignment(
596 - SPAN,
597 - op,
598 - left,
599 - self.convert_expression(&assign.right),
600 - )
601 - }
602 - Expression::SequenceExpression(seq) => {
603 - let exprs = self
604 - .builder
605 - .vec_from_iter(seq.expressions.iter().map(|e| self.convert_expression(e)));
606 - self.builder.expression_sequence(SPAN, exprs)
607 - }
608 - Expression::ArrowFunctionExpression(arrow) => self.convert_arrow_function(arrow),
609 - Expression::FunctionExpression(func) => {
610 - let f = self.convert_function_expr(func);
611 - oxc::Expression::FunctionExpression(self.builder.alloc(f))
612 - }
613 - Expression::ObjectExpression(obj) => {
614 - let properties = self.builder.vec_from_iter(
615 - obj.properties
616 - .iter()
617 - .map(|p| self.convert_object_expression_property(p)),
618 - );
619 - self.builder.expression_object(SPAN, properties)
620 - }
621 - Expression::ArrayExpression(arr) => {
622 - let elements = self
623 - .builder
624 - .vec_from_iter(arr.elements.iter().map(|e| self.convert_array_element(e)));
625 - self.builder.expression_array(SPAN, elements)
626 - }
627 - Expression::NewExpression(n) => {
628 - let callee = self.convert_expression(&n.callee);
629 - let args = self.convert_arguments(&n.arguments);
630 - self.builder.expression_new(
631 - SPAN,
632 - callee,
633 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
634 - args,
635 - )
636 - }
637 - Expression::TemplateLiteral(tl) => {
638 - let template = self.convert_template_literal(tl);
639 - oxc::Expression::TemplateLiteral(self.builder.alloc(template))
640 - }
641 - Expression::TaggedTemplateExpression(tag) => {
642 - let t = self.convert_expression(&tag.tag);
643 - let quasi = self.convert_template_literal(&tag.quasi);
644 - self.builder.expression_tagged_template(
645 - SPAN,
646 - t,
647 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
648 - quasi,
649 - )
650 - }
651 - Expression::AwaitExpression(a) => self
652 - .builder
653 - .expression_await(SPAN, self.convert_expression(&a.argument)),
654 - Expression::YieldExpression(y) => self.builder.expression_yield(
655 - SPAN,
656 - y.delegate,
657 - y.argument.as_ref().map(|a| self.convert_expression(a)),
658 - ),
659 - Expression::SpreadElement(s) => {
660 - // SpreadElement can't be a standalone expression in OXC.
661 - // Return the argument directly as a fallback.
662 - self.convert_expression(&s.argument)
663 - }
664 - Expression::MetaProperty(mp) => {
665 - let meta = self.builder.identifier_name(SPAN, self.atom(&mp.meta.name));
666 - let property = self
667 - .builder
668 - .identifier_name(SPAN, self.atom(&mp.property.name));
669 - self.builder.expression_meta_property(SPAN, meta, property)
670 - }
671 - Expression::ClassExpression(c) => {
672 - let class = self.convert_class_to_oxc(c, oxc::ClassType::ClassExpression);
673 - oxc::Expression::ClassExpression(self.builder.alloc(class))
674 - }
675 - Expression::PrivateName(_) => self
676 - .builder
677 - .expression_identifier(SPAN, self.atom("__private__")),
678 - Expression::Super(_) => self.builder.expression_super(SPAN),
679 - Expression::Import(_) => self
680 - .builder
681 - .expression_identifier(SPAN, self.atom("__import__")),
682 - Expression::ThisExpression(_) => self.builder.expression_this(SPAN),
683 - Expression::ParenthesizedExpression(p) => self
684 - .builder
685 - .expression_parenthesized(SPAN, self.convert_expression(&p.expression)),
686 - Expression::JSXElement(el) => {
687 - let element = self.convert_jsx_element(el);
688 - oxc::Expression::JSXElement(self.builder.alloc(element))
689 - }
690 - Expression::JSXFragment(frag) => {
691 - let fragment = self.convert_jsx_fragment(frag);
692 - oxc::Expression::JSXFragment(self.builder.alloc(fragment))
693 - }
694 - // TS expressions - strip the type wrapper, keep the expression
695 - Expression::TSAsExpression(e) => self.convert_expression(&e.expression),
696 - Expression::TSSatisfiesExpression(e) => self.convert_expression(&e.expression),
697 - Expression::TSNonNullExpression(e) => self
698 - .builder
699 - .expression_ts_non_null(SPAN, self.convert_expression(&e.expression)),
700 - Expression::TSTypeAssertion(e) => self.convert_expression(&e.expression),
701 - Expression::TSInstantiationExpression(e) => self.convert_expression(&e.expression),
702 - Expression::TypeCastExpression(e) => self.convert_expression(&e.expression),
703 - Expression::AssignmentPattern(p) => {
704 - let left = self.convert_pattern_to_assignment_target(&p.left);
705 - self.builder.expression_assignment(
706 - SPAN,
707 - oxc_syntax::operator::AssignmentOperator::Assign,
708 - left,
709 - self.convert_expression(&p.right),
710 - )
711 - }
712 - }
713 - }
714 -
715 - /// Convert an expression that may be used inside a chain (optional chaining).
716 - fn convert_expression_for_chain(&self, expr: &Expression) -> oxc::Expression<'a> {
717 - match expr {
718 - Expression::OptionalMemberExpression(m) => {
719 - self.convert_optional_member_to_expression(m)
720 - }
721 - Expression::OptionalCallExpression(call) => {
722 - let callee = self.convert_expression_for_chain(&call.callee);
723 - let args = self.convert_arguments(&call.arguments);
724 - let call_expr = self.builder.call_expression(
725 - SPAN,
726 - callee,
727 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
728 - args,
729 - call.optional,
730 - );
731 - oxc::Expression::CallExpression(self.builder.alloc(call_expr))
732 - }
733 - _ => self.convert_expression(expr),
734 - }
735 - }
736 -
737 - fn convert_member_expression(&self, m: &MemberExpression) -> oxc::Expression<'a> {
738 - let object = self.convert_expression(&m.object);
739 - if m.computed {
740 - let property = self.convert_expression(&m.property);
741 - oxc::Expression::ComputedMemberExpression(
742 - self.builder.alloc(
743 - self.builder
744 - .computed_member_expression(SPAN, object, property, false),
745 - ),
746 - )
747 - } else {
748 - let prop_name = self.expression_to_identifier_name(&m.property);
749 - oxc::Expression::StaticMemberExpression(
750 - self.builder.alloc(
751 - self.builder
752 - .static_member_expression(SPAN, object, prop_name, false),
753 - ),
754 - )
755 - }
756 - }
757 -
758 - fn convert_optional_member_to_chain_element(
759 - &self,
760 - m: &OptionalMemberExpression,
761 - ) -> oxc::ChainElement<'a> {
762 - let object = self.convert_expression_for_chain(&m.object);
763 - if m.computed {
764 - let property = self.convert_expression(&m.property);
765 - oxc::ChainElement::ComputedMemberExpression(
766 - self.builder.alloc(
767 - self.builder
768 - .computed_member_expression(SPAN, object, property, m.optional),
769 - ),
770 - )
771 - } else {
772 - let prop_name = self.expression_to_identifier_name(&m.property);
773 - oxc::ChainElement::StaticMemberExpression(
774 - self.builder.alloc(
775 - self.builder
776 - .static_member_expression(SPAN, object, prop_name, m.optional),
777 - ),
778 - )
779 - }
780 - }
781 -
782 - fn convert_optional_member_to_expression(
783 - &self,
784 - m: &OptionalMemberExpression,
785 - ) -> oxc::Expression<'a> {
786 - let object = self.convert_expression_for_chain(&m.object);
787 - if m.computed {
788 - let property = self.convert_expression(&m.property);
789 - oxc::Expression::ComputedMemberExpression(
790 - self.builder.alloc(
791 - self.builder
792 - .computed_member_expression(SPAN, object, property, m.optional),
793 - ),
794 - )
795 - } else {
796 - let prop_name = self.expression_to_identifier_name(&m.property);
797 - oxc::Expression::StaticMemberExpression(
798 - self.builder.alloc(
799 - self.builder
800 - .static_member_expression(SPAN, object, prop_name, m.optional),
801 - ),
802 - )
803 - }
804 - }
805 -
806 - fn expression_to_identifier_name(&self, expr: &Expression) -> oxc::IdentifierName<'a> {
807 - match expr {
808 - Expression::Identifier(id) => self.builder.identifier_name(SPAN, self.atom(&id.name)),
809 - _ => self.builder.identifier_name(SPAN, self.atom("__unknown__")),
810 - }
811 - }
812 -
813 - fn convert_arguments(&self, args: &[Expression]) -> oxc_allocator::Vec<'a, oxc::Argument<'a>> {
814 - self.builder
815 - .vec_from_iter(args.iter().map(|a| self.convert_argument(a)))
816 - }
817 -
818 - fn convert_argument(&self, arg: &Expression) -> oxc::Argument<'a> {
819 - match arg {
820 - Expression::SpreadElement(s) => self
821 - .builder
822 - .argument_spread_element(SPAN, self.convert_expression(&s.argument)),
823 - _ => oxc::Argument::from(self.convert_expression(arg)),
824 - }
825 - }
826 -
827 - fn convert_array_element(&self, elem: &Option<Expression>) -> oxc::ArrayExpressionElement<'a> {
828 - match elem {
829 - None => self.builder.array_expression_element_elision(SPAN),
830 - Some(Expression::SpreadElement(s)) => {
831 - self.builder.array_expression_element_spread_element(
832 - SPAN,
833 - self.convert_expression(&s.argument),
834 - )
835 - }
836 - Some(e) => oxc::ArrayExpressionElement::from(self.convert_expression(e)),
837 - }
838 - }
839 -
840 - fn convert_object_expression_property(
841 - &self,
842 - prop: &ObjectExpressionProperty,
843 - ) -> oxc::ObjectPropertyKind<'a> {
844 - match prop {
845 - ObjectExpressionProperty::ObjectProperty(p) => {
846 - let key = self.convert_expression_to_property_key(&p.key);
847 - let value = self.convert_expression(&p.value);
848 - let method = p.method.unwrap_or(false);
849 - let obj_prop = self.builder.object_property(
850 - SPAN,
851 - oxc::PropertyKind::Init,
852 - key,
853 - value,
854 - method,
855 - p.shorthand,
856 - p.computed,
857 - );
858 - oxc::ObjectPropertyKind::ObjectProperty(self.builder.alloc(obj_prop))
859 - }
860 - ObjectExpressionProperty::ObjectMethod(m) => {
861 - let kind = match m.kind {
862 - ObjectMethodKind::Method => oxc::PropertyKind::Init,
863 - ObjectMethodKind::Get => oxc::PropertyKind::Get,
864 - ObjectMethodKind::Set => oxc::PropertyKind::Set,
865 - };
866 - let key = self.convert_expression_to_property_key(&m.key);
867 - let func = self.convert_object_method_to_function(m);
868 - let func_expr = oxc::Expression::FunctionExpression(self.builder.alloc(func));
869 - let obj_prop = self.builder.object_property(
870 - SPAN, kind, key, func_expr, m.method, false, // shorthand
871 - m.computed,
872 - );
873 - oxc::ObjectPropertyKind::ObjectProperty(self.builder.alloc(obj_prop))
874 - }
875 - ObjectExpressionProperty::SpreadElement(s) => {
876 - let spread = self
877 - .builder
878 - .spread_element(SPAN, self.convert_expression(&s.argument));
879 - oxc::ObjectPropertyKind::SpreadProperty(self.builder.alloc(spread))
880 - }
881 - }
882 - }
883 -
884 - fn convert_expression_to_property_key(&self, expr: &Expression) -> oxc::PropertyKey<'a> {
885 - match expr {
886 - Expression::Identifier(id) => self
887 - .builder
888 - .property_key_static_identifier(SPAN, self.atom(&id.name)),
889 - Expression::StringLiteral(s) => {
890 - let lit = self.builder.string_literal(SPAN, self.atom(&s.value), None);
891 - oxc::PropertyKey::StringLiteral(self.builder.alloc(lit))
892 - }
893 - Expression::NumericLiteral(n) => {
894 - let lit =
895 - self.builder
896 - .numeric_literal(SPAN, n.value, None, oxc::NumberBase::Decimal);
897 - oxc::PropertyKey::NumericLiteral(self.builder.alloc(lit))
898 - }
899 - Expression::PrivateName(p) => self
900 - .builder
901 - .property_key_private_identifier(SPAN, self.atom(&p.id.name)),
902 - _ => oxc::PropertyKey::from(self.convert_expression(expr)),
903 - }
904 - }
905 -
906 - fn convert_template_literal(
907 - &self,
908 - tl: &react_compiler_ast::expressions::TemplateLiteral,
909 - ) -> oxc::TemplateLiteral<'a> {
910 - let quasis = self.builder.vec_from_iter(tl.quasis.iter().map(|q| {
911 - let raw = self.atom(&q.value.raw);
912 - let cooked = q.value.cooked.as_ref().map(|c| self.atom(c));
913 - let value = oxc::TemplateElementValue { raw, cooked };
914 - self.builder.template_element(SPAN, value, q.tail, false)
915 - }));
916 - let expressions = self
917 - .builder
918 - .vec_from_iter(tl.expressions.iter().map(|e| self.convert_expression(e)));
919 - self.builder.template_literal(SPAN, quasis, expressions)
920 - }
921 -
922 - // ===== Functions =====
923 -
924 - fn convert_function_decl(
925 - &self,
926 - f: &FunctionDeclaration,
927 - fn_type: oxc::FunctionType,
928 - ) -> oxc::Function<'a> {
929 - let id =
930 - f.id.as_ref()
931 - .map(|id| self.builder.binding_identifier(SPAN, self.atom(&id.name)));
932 - let params = self.convert_params_to_formal_parameters(&f.params);
933 - let body = self.convert_block_to_function_body(&f.body);
934 - self.builder.function(
935 - SPAN,
936 - fn_type,
937 - id,
938 - f.generator,
939 - f.is_async,
940 - f.declare.unwrap_or(false),
941 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
942 - None::<oxc_allocator::Box<'a, oxc::TSThisParameter<'a>>>,
943 - params,
944 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
945 - Some(body),
946 - )
947 - }
948 -
949 - fn convert_class_declaration(&self, c: &ClassDeclaration) -> oxc::Class<'a> {
950 - let id =
951 - c.id.as_ref()
952 - .map(|id| self.builder.binding_identifier(SPAN, self.atom(&id.name)));
953 - let super_class = c.super_class.as_ref().map(|s| self.convert_expression(s));
954 - let body = self.builder.class_body(SPAN, self.builder.vec());
955 - self.builder.class(
956 - SPAN,
957 - oxc::ClassType::ClassDeclaration,
958 - self.builder.vec(), // decorators
959 - id,
960 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
961 - super_class,
962 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
963 - self.builder.vec(), // implements
964 - body,
965 - c.is_abstract.unwrap_or(false),
966 - c.declare.unwrap_or(false),
967 - )
968 - }
969 -
970 - fn convert_class_to_oxc(
971 - &self,
972 - c: &react_compiler_ast::expressions::ClassExpression,
973 - class_type: oxc::ClassType,
974 - ) -> oxc::Class<'a> {
975 - let id =
976 - c.id.as_ref()
977 - .map(|id| self.builder.binding_identifier(SPAN, self.atom(&id.name)));
978 - let super_class = c.super_class.as_ref().map(|s| self.convert_expression(s));
979 - let body = self.builder.class_body(SPAN, self.builder.vec());
980 - self.builder.class(
981 - SPAN,
982 - class_type,
983 - self.builder.vec(), // decorators
984 - id,
985 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
986 - super_class,
987 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
988 - self.builder.vec(), // implements
989 - body,
990 - false, // is_abstract
991 - false, // declare
992 - )
993 - }
994 -
995 - fn convert_function_expr(&self, f: &FunctionExpression) -> oxc::Function<'a> {
996 - let id =
997 - f.id.as_ref()
998 - .map(|id| self.builder.binding_identifier(SPAN, self.atom(&id.name)));
999 - let params = self.convert_params_to_formal_parameters(&f.params);
1000 - let body = self.convert_block_to_function_body(&f.body);
1001 - self.builder.function(
1002 - SPAN,
1003 - oxc::FunctionType::FunctionExpression,
1004 - id,
1005 - f.generator,
1006 - f.is_async,
1007 - false,
1008 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
1009 - None::<oxc_allocator::Box<'a, oxc::TSThisParameter<'a>>>,
1010 - params,
1011 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1012 - Some(body),
1013 - )
1014 - }
1015 -
1016 - fn convert_object_method_to_function(&self, m: &ObjectMethod) -> oxc::Function<'a> {
1017 - let params = self.convert_params_to_formal_parameters(&m.params);
1018 - let body = self.convert_block_to_function_body(&m.body);
1019 - self.builder.function(
1020 - SPAN,
1021 - oxc::FunctionType::FunctionExpression,
1022 - None,
1023 - m.generator,
1024 - m.is_async,
1025 - false,
1026 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
1027 - None::<oxc_allocator::Box<'a, oxc::TSThisParameter<'a>>>,
1028 - params,
1029 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1030 - Some(body),
1031 - )
1032 - }
1033 -
1034 - fn convert_arrow_function(&self, arrow: &ArrowFunctionExpression) -> oxc::Expression<'a> {
1035 - let is_expression = arrow.expression.unwrap_or(false);
1036 - let params = self.convert_params_to_formal_parameters(&arrow.params);
1037 -
1038 - let body = match &*arrow.body {
1039 - ArrowFunctionBody::BlockStatement(block) => self.convert_block_to_function_body(block),
1040 - ArrowFunctionBody::Expression(expr) => {
1041 - let oxc_expr = self.convert_expression(expr);
1042 - let stmt = self.builder.statement_expression(SPAN, oxc_expr);
1043 - let stmts = self.builder.vec_from_iter(std::iter::once(stmt));
1044 - self.builder.function_body(SPAN, self.builder.vec(), stmts)
1045 - }
1046 - };
1047 -
1048 - self.builder.expression_arrow_function(
1049 - SPAN,
1050 - is_expression,
1051 - arrow.is_async,
1052 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterDeclaration<'a>>>,
1053 - params,
1054 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1055 - body,
1056 - )
1057 - }
1058 -
1059 - fn convert_block_to_function_body(&self, block: &BlockStatement) -> oxc::FunctionBody<'a> {
1060 - let stmts = self.convert_statement_vec(&block.body);
1061 - let directives = self.convert_directives(&block.directives);
1062 - self.builder.function_body(SPAN, directives, stmts)
1063 - }
1064 -
1065 - fn convert_params_to_formal_parameters(
1066 - &self,
1067 - params: &[PatternLike],
1068 - ) -> oxc::FormalParameters<'a> {
1069 - let mut items: Vec<oxc::FormalParameter<'a>> = Vec::new();
1070 - let mut rest: Option<oxc::FormalParameterRest<'a>> = None;
1071 -
1072 - for param in params {
1073 - match param {
1074 - PatternLike::RestElement(r) => {
1075 - let arg = self.convert_pattern_to_binding_pattern(&r.argument);
1076 - let rest_elem = self.builder.binding_rest_element(SPAN, arg);
1077 - rest = Some(self.builder.formal_parameter_rest(
1078 - SPAN,
1079 - self.builder.vec(),
1080 - rest_elem,
1081 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1082 - ));
1083 - }
1084 - PatternLike::AssignmentPattern(ap) => {
1085 - // OXC stores default parameter values in FormalParameter.initializer
1086 - // rather than using BindingPattern::AssignmentPattern (which OXC considers
1087 - // invalid in FormalParameter position).
1088 - let left = self.convert_pattern_to_binding_pattern(&ap.left);
1089 - let right = self.convert_expression(&ap.right);
1090 - let initializer = Some(oxc_allocator::Box::new_in(right, self.allocator));
1091 - let fp = self.builder.formal_parameter(
1092 - SPAN,
1093 - self.builder.vec(), // decorators
1094 - left,
1095 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1096 - initializer,
1097 - false, // optional
1098 - None, // accessibility
1099 - false, // readonly
1100 - false, // override
1101 - );
1102 - items.push(fp);
1103 - }
1104 - _ => {
1105 - let pattern = self.convert_pattern_to_binding_pattern(param);
1106 - let fp = self.builder.formal_parameter(
1107 - SPAN,
1108 - self.builder.vec(), // decorators
1109 - pattern,
1110 - None::<oxc_allocator::Box<'a, oxc::TSTypeAnnotation<'a>>>,
1111 - None::<oxc_allocator::Box<'a, oxc::Expression<'a>>>,
1112 - false, // optional
1113 - None, // accessibility
1114 - false, // readonly
1115 - false, // override
1116 - );
1117 - items.push(fp);
1118 - }
1119 - }
1120 - }
1121 -
1122 - let items_vec = self.builder.vec_from_iter(items);
1123 - self.builder.formal_parameters(
1124 - SPAN,
1125 - oxc::FormalParameterKind::FormalParameter,
1126 - items_vec,
1127 - rest,
1128 - )
1129 - }
1130 -
1131 - // ===== Patterns → BindingPattern =====
1132 -
1133 - fn convert_pattern_to_binding_pattern(&self, pattern: &PatternLike) -> oxc::BindingPattern<'a> {
1134 - match pattern {
1135 - PatternLike::Identifier(id) => self
1136 - .builder
1137 - .binding_pattern_binding_identifier(SPAN, self.atom(&id.name)),
1138 - PatternLike::ObjectPattern(obj) => {
1139 - let mut properties: Vec<oxc::BindingProperty<'a>> = Vec::new();
1140 - let mut rest: Option<oxc::BindingRestElement<'a>> = None;
1141 -
1142 - for prop in &obj.properties {
1143 - match prop {
1144 - ObjectPatternProperty::ObjectProperty(p) => {
1145 - let key = self.convert_expression_to_property_key(&p.key);
1146 - let value = self.convert_pattern_to_binding_pattern(&p.value);
1147 - let bp = self.builder.binding_property(
1148 - SPAN,
1149 - key,
1150 - value,
1151 - p.shorthand,
1152 - p.computed,
1153 - );
1154 - properties.push(bp);
1155 - }
1156 - ObjectPatternProperty::RestElement(r) => {
1157 - let arg = self.convert_pattern_to_binding_pattern(&r.argument);
1158 - rest = Some(self.builder.binding_rest_element(SPAN, arg));
1159 - }
1160 - }
1161 - }
1162 -
1163 - let props_vec = self.builder.vec_from_iter(properties);
1164 - self.builder
1165 - .binding_pattern_object_pattern(SPAN, props_vec, rest)
1166 - }
1167 - PatternLike::ArrayPattern(arr) => {
1168 - let mut elements: Vec<Option<oxc::BindingPattern<'a>>> = Vec::new();
1169 - let mut rest: Option<oxc::BindingRestElement<'a>> = None;
1170 -
1171 - for elem in &arr.elements {
1172 - match elem {
1173 - None => elements.push(None),
1174 - Some(PatternLike::RestElement(r)) => {
1175 - let arg = self.convert_pattern_to_binding_pattern(&r.argument);
1176 - rest = Some(self.builder.binding_rest_element(SPAN, arg));
1177 - }
1178 - Some(p) => {
1179 - elements.push(Some(self.convert_pattern_to_binding_pattern(p)));
1180 - }
1181 - }
1182 - }
1183 -
1184 - let elems_vec = self.builder.vec_from_iter(elements);
1185 - self.builder
1186 - .binding_pattern_array_pattern(SPAN, elems_vec, rest)
1187 - }
1188 - PatternLike::AssignmentPattern(ap) => {
1189 - let left = self.convert_pattern_to_binding_pattern(&ap.left);
1190 - let right = self.convert_expression(&ap.right);
1191 - self.builder
1192 - .binding_pattern_assignment_pattern(SPAN, left, right)
1193 - }
1194 - PatternLike::RestElement(r) => self.convert_pattern_to_binding_pattern(&r.argument),
1195 - PatternLike::MemberExpression(_)
1196 - | PatternLike::TSAsExpression(_)
1197 - | PatternLike::TSSatisfiesExpression(_)
1198 - | PatternLike::TSNonNullExpression(_)
1199 - | PatternLike::TSTypeAssertion(_)
1200 - | PatternLike::TypeCastExpression(_) => self
1201 - .builder
1202 - .binding_pattern_binding_identifier(SPAN, self.atom("__member_pattern__")),
1203 - }
1204 - }
1205 -
1206 - // ===== Patterns → AssignmentTarget =====
1207 -
1208 - fn convert_pattern_to_assignment_target(
1209 - &self,
1210 - pattern: &PatternLike,
1211 - ) -> oxc::AssignmentTarget<'a> {
1212 - match pattern {
1213 - PatternLike::Identifier(id) => self
1214 - .builder
1215 - .simple_assignment_target_assignment_target_identifier(SPAN, self.atom(&id.name))
1216 - .into(),
1217 - PatternLike::MemberExpression(m) => {
1218 - let object = self.convert_expression(&m.object);
1219 - if m.computed {
1220 - let property = self.convert_expression(&m.property);
1221 - let mem = self
1222 - .builder
1223 - .computed_member_expression(SPAN, object, property, false);
1224 - oxc::AssignmentTarget::ComputedMemberExpression(self.builder.alloc(mem))
1225 - } else {
1226 - let prop_name = self.expression_to_identifier_name(&m.property);
1227 - let mem = self
1228 - .builder
1229 - .static_member_expression(SPAN, object, prop_name, false);
1230 - oxc::AssignmentTarget::StaticMemberExpression(self.builder.alloc(mem))
1231 - }
1232 - }
1233 - PatternLike::ObjectPattern(obj) => {
1234 - let mut properties: Vec<oxc::AssignmentTargetProperty<'a>> = Vec::new();
1235 - let mut rest: Option<oxc::AssignmentTargetRest<'a>> = None;
1236 -
1237 - for prop in &obj.properties {
1238 - match prop {
1239 - ObjectPatternProperty::ObjectProperty(p) => {
1240 - if p.shorthand {
1241 - // Shorthand: { x } means { x: x }
1242 - // Use AssignmentTargetPropertyIdentifier
1243 - if let Expression::Identifier(id) = &*p.key {
1244 - let binding = self
1245 - .builder
1246 - .identifier_reference(SPAN, self.atom(&id.name));
1247 - let init = match &*p.value {
1248 - PatternLike::AssignmentPattern(ap) => {
1249 - Some(self.convert_expression(&ap.right))
1250 - }
1251 - _ => None,
1252 - };
1253 - let atp = self
1254 - .builder
1255 - .assignment_target_property_assignment_target_property_identifier(
1256 - SPAN, binding, init,
1257 - );
1258 - properties.push(atp);
1259 - } else {
1260 - // Fallback to non-shorthand
1261 - let key = self.convert_expression_to_property_key(&p.key);
1262 - let binding = self
1263 - .convert_pattern_to_assignment_target_maybe_default(
1264 - &p.value,
1265 - );
1266 - let atp = self
1267 - .builder
1268 - .assignment_target_property_assignment_target_property_property(
1269 - SPAN, key, binding, p.computed,
1270 - );
1271 - properties.push(atp);
1272 - }
1273 - } else {
1274 - let key = self.convert_expression_to_property_key(&p.key);
1275 - let binding = self
1276 - .convert_pattern_to_assignment_target_maybe_default(&p.value);
1277 - let atp = self
1278 - .builder
1279 - .assignment_target_property_assignment_target_property_property(
1280 - SPAN, key, binding, p.computed,
1281 - );
1282 - properties.push(atp);
1283 - }
1284 - }
1285 - ObjectPatternProperty::RestElement(r) => {
1286 - let target = self.convert_pattern_to_assignment_target(&r.argument);
1287 - rest = Some(self.builder.assignment_target_rest(SPAN, target));
1288 - }
1289 - }
1290 - }
1291 -
1292 - let props_vec = self.builder.vec_from_iter(properties);
1293 - self.builder
1294 - .assignment_target_pattern_object_assignment_target(SPAN, props_vec, rest)
1295 - .into()
1296 - }
1297 - PatternLike::ArrayPattern(arr) => {
1298 - let mut elements: Vec<Option<oxc::AssignmentTargetMaybeDefault<'a>>> = Vec::new();
1299 - let mut rest: Option<oxc::AssignmentTargetRest<'a>> = None;
1300 -
1301 - for elem in &arr.elements {
1302 - match elem {
1303 - None => elements.push(None),
1304 - Some(PatternLike::RestElement(r)) => {
1305 - let target = self.convert_pattern_to_assignment_target(&r.argument);
1306 - rest = Some(self.builder.assignment_target_rest(SPAN, target));
1307 - }
1308 - Some(p) => {
1309 - elements.push(Some(
1310 - self.convert_pattern_to_assignment_target_maybe_default(p),
1311 - ));
1312 - }
1313 - }
1314 - }
1315 -
1316 - let elems_vec = self.builder.vec_from_iter(elements);
1317 - self.builder
1318 - .assignment_target_pattern_array_assignment_target(SPAN, elems_vec, rest)
1319 - .into()
1320 - }
1321 - PatternLike::AssignmentPattern(ap) => {
1322 - // For assignment LHS, use the left side
1323 - self.convert_pattern_to_assignment_target(&ap.left)
1324 - }
1325 - PatternLike::RestElement(r) => self.convert_pattern_to_assignment_target(&r.argument),
1326 - PatternLike::TSAsExpression(_)
1327 - | PatternLike::TSSatisfiesExpression(_)
1328 - | PatternLike::TSNonNullExpression(_)
1329 - | PatternLike::TSTypeAssertion(_)
1330 - | PatternLike::TypeCastExpression(_) => self
1331 - .builder
1332 - .simple_assignment_target_assignment_target_identifier(
1333 - SPAN,
1334 - self.atom("__unknown__"),
1335 - )
1336 - .into(),
1337 - }
1338 - }
1339 -
1340 - fn convert_pattern_to_assignment_target_maybe_default(
1341 - &self,
1342 - pattern: &PatternLike,
1343 - ) -> oxc::AssignmentTargetMaybeDefault<'a> {
1344 - match pattern {
1345 - PatternLike::AssignmentPattern(ap) => {
1346 - let binding = self.convert_pattern_to_assignment_target(&ap.left);
1347 - let init = self.convert_expression(&ap.right);
1348 - self.builder
1349 - .assignment_target_maybe_default_assignment_target_with_default(
1350 - SPAN, binding, init,
1351 - )
1352 - }
1353 - _ => {
1354 - let target = self.convert_pattern_to_assignment_target(pattern);
1355 - oxc::AssignmentTargetMaybeDefault::from(target)
1356 - }
1357 - }
1358 - }
1359 -
1360 - fn convert_expression_to_simple_assignment_target(
1361 - &self,
1362 - expr: &Expression,
1363 - ) -> oxc::SimpleAssignmentTarget<'a> {
1364 - match expr {
1365 - Expression::Identifier(id) => self
1366 - .builder
1367 - .simple_assignment_target_assignment_target_identifier(SPAN, self.atom(&id.name)),
1368 - Expression::MemberExpression(m) => {
1369 - let object = self.convert_expression(&m.object);
1370 - if m.computed {
1371 - let property = self.convert_expression(&m.property);
1372 - let mem = self
1373 - .builder
1374 - .computed_member_expression(SPAN, object, property, false);
1375 - oxc::SimpleAssignmentTarget::ComputedMemberExpression(self.builder.alloc(mem))
1376 - } else {
1377 - let prop_name = self.expression_to_identifier_name(&m.property);
1378 - let mem = self
1379 - .builder
1380 - .static_member_expression(SPAN, object, prop_name, false);
1381 - oxc::SimpleAssignmentTarget::StaticMemberExpression(self.builder.alloc(mem))
1382 - }
1383 - }
1384 - _ => self
1385 - .builder
1386 - .simple_assignment_target_assignment_target_identifier(
1387 - SPAN,
1388 - self.atom("__unknown__"),
1389 - ),
1390 - }
1391 - }
1392 -
1393 - // ===== JSX =====
1394 -
1395 - fn convert_jsx_element(&self, el: &JSXElement) -> oxc::JSXElement<'a> {
1396 - let opening = self.convert_jsx_opening_element(&el.opening_element);
1397 - let children = self
1398 - .builder
1399 - .vec_from_iter(el.children.iter().map(|c| self.convert_jsx_child(c)));
1400 - let closing = el
1401 - .closing_element
1402 - .as_ref()
1403 - .map(|c| self.convert_jsx_closing_element(c));
1404 - self.builder.jsx_element(SPAN, opening, children, closing)
1405 - }
1406 -
1407 - fn convert_jsx_opening_element(&self, el: &JSXOpeningElement) -> oxc::JSXOpeningElement<'a> {
1408 - let name = self.convert_jsx_element_name(&el.name);
1409 - let attrs = self.builder.vec_from_iter(
1410 - el.attributes
1411 - .iter()
1412 - .map(|a| self.convert_jsx_attribute_item(a)),
1413 - );
1414 - self.builder.jsx_opening_element(
1415 - SPAN,
1416 - name,
1417 - None::<oxc_allocator::Box<'a, oxc::TSTypeParameterInstantiation<'a>>>,
1418 - attrs,
1419 - )
1420 - }
1421 -
1422 - fn convert_jsx_closing_element(&self, el: &JSXClosingElement) -> oxc::JSXClosingElement<'a> {
1423 - let name = self.convert_jsx_element_name(&el.name);
1424 - self.builder.jsx_closing_element(SPAN, name)
1425 - }
1426 -
1427 - fn convert_jsx_element_name(&self, name: &JSXElementName) -> oxc::JSXElementName<'a> {
1428 - match name {
1429 - JSXElementName::JSXIdentifier(id) => {
1430 - let first_char = id.name.chars().next().unwrap_or('a');
1431 - if first_char.is_uppercase() || id.name.contains('.') {
1432 - self.builder
1433 - .jsx_element_name_identifier_reference(SPAN, self.atom(&id.name))
1434 - } else {
1435 - self.builder
1436 - .jsx_element_name_identifier(SPAN, self.atom(&id.name))
1437 - }
1438 - }
1439 - JSXElementName::JSXMemberExpression(m) => {
1440 - let member = self.convert_jsx_member_expression(m);
1441 - self.builder.jsx_element_name_member_expression(
1442 - SPAN,
1443 - member.object,
1444 - member.property,
1445 - )
1446 - }
1447 - JSXElementName::JSXNamespacedName(ns) => {
1448 - let namespace = self
1449 - .builder
1450 - .jsx_identifier(SPAN, self.atom(&ns.namespace.name));
1451 - let name = self.builder.jsx_identifier(SPAN, self.atom(&ns.name.name));
1452 - self.builder
1453 - .jsx_element_name_namespaced_name(SPAN, namespace, name)
1454 - }
1455 - }
1456 - }
1457 -
1458 - fn convert_jsx_member_expression(
1459 - &self,
1460 - m: &JSXMemberExpression,
1461 - ) -> oxc::JSXMemberExpression<'a> {
1462 - let object = self.convert_jsx_member_expression_object(&m.object);
1463 - let property = self
1464 - .builder
1465 - .jsx_identifier(SPAN, self.atom(&m.property.name));
1466 - self.builder.jsx_member_expression(SPAN, object, property)
1467 - }
1468 -
1469 - fn convert_jsx_member_expression_object(
1470 - &self,
1471 - obj: &JSXMemberExprObject,
1472 - ) -> oxc::JSXMemberExpressionObject<'a> {
1473 - match obj {
1474 - JSXMemberExprObject::JSXIdentifier(id) => self
1475 - .builder
1476 - .jsx_member_expression_object_identifier_reference(SPAN, self.atom(&id.name)),
1477 - JSXMemberExprObject::JSXMemberExpression(m) => {
1478 - let member = self.convert_jsx_member_expression(m);
1479 - self.builder.jsx_member_expression_object_member_expression(
1480 - SPAN,
1481 - member.object,
1482 - member.property,
1483 - )
1484 - }
1485 - }
1486 - }
1487 -
1488 - fn convert_jsx_attribute_item(&self, item: &JSXAttributeItem) -> oxc::JSXAttributeItem<'a> {
1489 - match item {
1490 - JSXAttributeItem::JSXAttribute(attr) => {
1491 - let name = self.convert_jsx_attribute_name(&attr.name);
1492 - let value = attr
1493 - .value
1494 - .as_ref()
1495 - .map(|v| self.convert_jsx_attribute_value(v));
1496 - self.builder.jsx_attribute_item_attribute(SPAN, name, value)
1497 - }
1498 - JSXAttributeItem::JSXSpreadAttribute(s) => self
1499 - .builder
1500 - .jsx_attribute_item_spread_attribute(SPAN, self.convert_expression(&s.argument)),
1501 - }
1502 - }
1503 -
1504 - fn convert_jsx_attribute_name(&self, name: &JSXAttributeName) -> oxc::JSXAttributeName<'a> {
1505 - match name {
1506 - JSXAttributeName::JSXIdentifier(id) => self
1507 - .builder
1508 - .jsx_attribute_name_identifier(SPAN, self.atom(&id.name)),
1509 - JSXAttributeName::JSXNamespacedName(ns) => {
1510 - let namespace = self
1511 - .builder
1512 - .jsx_identifier(SPAN, self.atom(&ns.namespace.name));
1513 - let name = self.builder.jsx_identifier(SPAN, self.atom(&ns.name.name));
1514 - self.builder
1515 - .jsx_attribute_name_namespaced_name(SPAN, namespace, name)
1516 - }
1517 - }
1518 - }
1519 -
1520 - fn convert_jsx_attribute_value(&self, value: &JSXAttributeValue) -> oxc::JSXAttributeValue<'a> {
1521 - match value {
1522 - JSXAttributeValue::StringLiteral(s) => {
1523 - self.builder
1524 - .jsx_attribute_value_string_literal(SPAN, self.atom(&s.value), None)
1525 - }
1526 - JSXAttributeValue::JSXExpressionContainer(ec) => {
1527 - let expr = self.convert_jsx_expression_container_expr(&ec.expression);
1528 - self.builder
1529 - .jsx_attribute_value_expression_container(SPAN, expr)
1530 - }
1531 - JSXAttributeValue::JSXElement(el) => {
1532 - let element = self.convert_jsx_element(el);
1533 - let opening = element.opening_element;
1534 - let closing = element.closing_element;
1535 - self.builder
1536 - .jsx_attribute_value_element(SPAN, opening, element.children, closing)
1537 - }
1538 - JSXAttributeValue::JSXFragment(frag) => {
1539 - let fragment = self.convert_jsx_fragment(frag);
1540 - self.builder.jsx_attribute_value_fragment(
1541 - SPAN,
1542 - fragment.opening_fragment,
1543 - fragment.children,
1544 - fragment.closing_fragment,
1545 - )
1546 - }
1547 - }
1548 - }
1549 -
1550 - fn convert_jsx_expression_container_expr(
1551 - &self,
1552 - expr: &JSXExpressionContainerExpr,
1553 - ) -> oxc::JSXExpression<'a> {
1554 - match expr {
1555 - JSXExpressionContainerExpr::JSXEmptyExpression(_) => {
1556 - self.builder.jsx_expression_empty_expression(SPAN)
1557 - }
1558 - JSXExpressionContainerExpr::Expression(e) => {
1559 - oxc::JSXExpression::from(self.convert_expression(e))
1560 - }
1561 - }
1562 - }
1563 -
1564 - fn convert_jsx_child(&self, child: &JSXChild) -> oxc::JSXChild<'a> {
1565 - match child {
1566 - JSXChild::JSXText(t) => self.builder.jsx_child_text(SPAN, self.atom(&t.value), None),
1567 - JSXChild::JSXElement(el) => {
1568 - let element = self.convert_jsx_element(el);
1569 - let opening = element.opening_element;
1570 - let closing = element.closing_element;
1571 - self.builder
1572 - .jsx_child_element(SPAN, opening, element.children, closing)
1573 - }
1574 - JSXChild::JSXFragment(frag) => {
1575 - let fragment = self.convert_jsx_fragment(frag);
1576 - self.builder.jsx_child_fragment(
1577 - SPAN,
1578 - fragment.opening_fragment,
1579 - fragment.children,
1580 - fragment.closing_fragment,
1581 - )
1582 - }
1583 - JSXChild::JSXExpressionContainer(ec) => {
1584 - let expr = self.convert_jsx_expression_container_expr(&ec.expression);
1585 - self.builder.jsx_child_expression_container(SPAN, expr)
1586 - }
1587 - JSXChild::JSXSpreadChild(s) => self
1588 - .builder
1589 - .jsx_child_spread(SPAN, self.convert_expression(&s.expression)),
1590 - }
1591 - }
1592 -
1593 - fn convert_jsx_fragment(&self, frag: &JSXFragment) -> oxc::JSXFragment<'a> {
1594 - let opening = self.builder.jsx_opening_fragment(SPAN);
1595 - let closing = self.builder.jsx_closing_fragment(SPAN);
1596 - let children = self
1597 - .builder
1598 - .vec_from_iter(frag.children.iter().map(|c| self.convert_jsx_child(c)));
1599 - self.builder.jsx_fragment(SPAN, opening, children, closing)
1600 - }
1601 -
1602 - // ===== Import/Export =====
1603 -
1604 - fn convert_import_declaration(&self, decl: &ImportDeclaration) -> oxc::ImportDeclaration<'a> {
1605 - let specifiers = self.builder.vec_from_iter(
1606 - decl.specifiers
1607 - .iter()
1608 - .map(|s| self.convert_import_specifier(s)),
1609 - );
1610 - let source = self
1611 - .builder
1612 - .string_literal(SPAN, self.atom(&decl.source.value), None);
1613 - let import_kind = match decl.import_kind.as_ref() {
1614 - Some(ImportKind::Type) => oxc::ImportOrExportKind::Type,
1615 - _ => oxc::ImportOrExportKind::Value,
1616 - };
1617 - self.builder.import_declaration(
1618 - SPAN,
1619 - Some(specifiers),
1620 - source,
1621 - None, // phase
1622 - None::<oxc_allocator::Box<'a, oxc::WithClause<'a>>>,
1623 - import_kind,
1624 - )
1625 - }
1626 -
1627 - fn convert_import_specifier(
1628 - &self,
1629 - spec: &react_compiler_ast::declarations::ImportSpecifier,
1630 - ) -> oxc::ImportDeclarationSpecifier<'a> {
1631 - match spec {
1632 - react_compiler_ast::declarations::ImportSpecifier::ImportSpecifier(s) => {
1633 - let local = self
1634 - .builder
1635 - .binding_identifier(SPAN, self.atom(&s.local.name));
1636 - let imported = self.convert_module_export_name(&s.imported);
1637 - let import_kind = match s.import_kind.as_ref() {
1638 - Some(ImportKind::Type) => oxc::ImportOrExportKind::Type,
1639 - _ => oxc::ImportOrExportKind::Value,
1640 - };
1641 - let is = self
1642 - .builder
1643 - .import_specifier(SPAN, imported, local, import_kind);
1644 - oxc::ImportDeclarationSpecifier::ImportSpecifier(self.builder.alloc(is))
1645 - }
1646 - react_compiler_ast::declarations::ImportSpecifier::ImportDefaultSpecifier(s) => {
1647 - let local = self
1648 - .builder
1649 - .binding_identifier(SPAN, self.atom(&s.local.name));
1650 - let ids = self.builder.import_default_specifier(SPAN, local);
1651 - oxc::ImportDeclarationSpecifier::ImportDefaultSpecifier(self.builder.alloc(ids))
1652 - }
1653 - react_compiler_ast::declarations::ImportSpecifier::ImportNamespaceSpecifier(s) => {
1654 - let local = self
1655 - .builder
1656 - .binding_identifier(SPAN, self.atom(&s.local.name));
1657 - let ins = self.builder.import_namespace_specifier(SPAN, local);
1658 - oxc::ImportDeclarationSpecifier::ImportNamespaceSpecifier(self.builder.alloc(ins))
1659 - }
1660 - }
1661 - }
1662 -
1663 - fn convert_module_export_name(
1664 - &self,
1665 - name: &react_compiler_ast::declarations::ModuleExportName,
1666 - ) -> oxc::ModuleExportName<'a> {
1667 - match name {
1668 - react_compiler_ast::declarations::ModuleExportName::Identifier(id) => {
1669 - oxc::ModuleExportName::IdentifierName(
1670 - self.builder.identifier_name(SPAN, self.atom(&id.name)),
1671 - )
1672 - }
1673 - react_compiler_ast::declarations::ModuleExportName::StringLiteral(s) => {
1674 - oxc::ModuleExportName::StringLiteral(self.builder.string_literal(
1675 - SPAN,
1676 - self.atom(&s.value),
1677 - None,
1678 - ))
1679 - }
1680 - }
1681 - }
1682 -
1683 - fn convert_export_named_declaration(
1684 - &self,
1685 - decl: &ExportNamedDeclaration,
1686 - ) -> oxc::ExportNamedDeclaration<'a> {
1687 - let declaration = decl
1688 - .declaration
1689 - .as_ref()
1690 - .map(|d| self.convert_declaration(d));
1691 - let specifiers = self.builder.vec_from_iter(
1692 - decl.specifiers
1693 - .iter()
1694 - .map(|s| self.convert_export_specifier(s)),
1695 - );
1696 - let source = decl
1697 - .source
1698 - .as_ref()
1699 - .map(|s| self.builder.string_literal(SPAN, self.atom(&s.value), None));
1700 - let export_kind = match decl.export_kind.as_ref() {
1701 - Some(ExportKind::Type) => oxc::ImportOrExportKind::Type,
1702 - _ => oxc::ImportOrExportKind::Value,
1703 - };
1704 - self.builder.export_named_declaration(
1705 - SPAN,
1706 - declaration,
1707 - specifiers,
1708 - source,
1709 - export_kind,
1710 - None::<oxc_allocator::Box<'a, oxc::WithClause<'a>>>,
1711 - )
1712 - }
1713 -
1714 - fn convert_declaration(&self, decl: &Declaration) -> oxc::Declaration<'a> {
1715 - match decl {
1716 - Declaration::FunctionDeclaration(f) => {
1717 - let func = self.convert_function_decl(f, oxc::FunctionType::FunctionDeclaration);
1718 - oxc::Declaration::FunctionDeclaration(self.builder.alloc(func))
1719 - }
1720 - Declaration::VariableDeclaration(v) => {
1721 - let d = self.convert_variable_declaration(v);
1722 - oxc::Declaration::VariableDeclaration(self.builder.alloc(d))
1723 - }
1724 - Declaration::ClassDeclaration(c) => {
1725 - let class = self.convert_class_declaration(c);
1726 - oxc::Declaration::ClassDeclaration(self.builder.alloc(class))
1727 - }
1728 - _ => {
1729 - let d = self.builder.variable_declaration(
1730 - SPAN,
1731 - oxc::VariableDeclarationKind::Const,
1732 - self.builder.vec(),
1733 - true,
1734 - );
1735 - oxc::Declaration::VariableDeclaration(self.builder.alloc(d))
1736 - }
1737 - }
1738 - }
1739 -
1740 - fn convert_export_specifier(
1741 - &self,
1742 - spec: &react_compiler_ast::declarations::ExportSpecifier,
1743 - ) -> oxc::ExportSpecifier<'a> {
1744 - match spec {
1745 - react_compiler_ast::declarations::ExportSpecifier::ExportSpecifier(s) => {
1746 - let local = self.convert_module_export_name(&s.local);
1747 - let exported = self.convert_module_export_name(&s.exported);
1748 - let export_kind = match s.export_kind.as_ref() {
1749 - Some(ExportKind::Type) => oxc::ImportOrExportKind::Type,
1750 - _ => oxc::ImportOrExportKind::Value,
1751 - };
1752 - self.builder
1753 - .export_specifier(SPAN, local, exported, export_kind)
1754 - }
1755 - react_compiler_ast::declarations::ExportSpecifier::ExportDefaultSpecifier(s) => {
1756 - let name = oxc::ModuleExportName::IdentifierName(
1757 - self.builder
1758 - .identifier_name(SPAN, self.atom(&s.exported.name)),
1759 - );
1760 - let default_name = oxc::ModuleExportName::IdentifierName(
1761 - self.builder.identifier_name(SPAN, self.atom("default")),
1762 - );
1763 - self.builder.export_specifier(
1764 - SPAN,
1765 - name,
1766 - default_name,
1767 - oxc::ImportOrExportKind::Value,
1768 - )
1769 - }
1770 - react_compiler_ast::declarations::ExportSpecifier::ExportNamespaceSpecifier(s) => {
1771 - let exported = self.convert_module_export_name(&s.exported);
1772 - let star = oxc::ModuleExportName::IdentifierName(
1773 - self.builder.identifier_name(SPAN, self.atom("*")),
1774 - );
1775 - self.builder
1776 - .export_specifier(SPAN, star, exported, oxc::ImportOrExportKind::Value)
1777 - }
1778 - }
1779 - }
1780 -
1781 - fn convert_export_default_declaration(
1782 - &self,
1783 - decl: &ExportDefaultDeclaration,
1784 - ) -> oxc::ExportDefaultDeclaration<'a> {
1785 - let declaration = self.convert_export_default_decl(&decl.declaration);
1786 - self.builder.export_default_declaration(SPAN, declaration)
1787 - }
1788 -
1789 - fn convert_export_default_decl(
1790 - &self,
1791 - decl: &ExportDefaultDecl,
1792 - ) -> oxc::ExportDefaultDeclarationKind<'a> {
1793 - match decl {
1794 - ExportDefaultDecl::FunctionDeclaration(f) => {
1795 - let func = self.convert_function_decl(f, oxc::FunctionType::FunctionDeclaration);
1796 - oxc::ExportDefaultDeclarationKind::FunctionDeclaration(self.builder.alloc(func))
1797 - }
1798 - ExportDefaultDecl::ClassDeclaration(c) => {
1799 - let class = self.convert_class_declaration(c);
1800 - oxc::ExportDefaultDeclarationKind::ClassDeclaration(self.builder.alloc(class))
1801 - }
1802 - ExportDefaultDecl::EnumDeclaration(_) => {
1803 - // Flow enum declarations cannot be represented in OXC AST;
1804 - // emit a null placeholder to preserve the export shape.
1805 - oxc::ExportDefaultDeclarationKind::from(
1806 - self.builder.expression_null_literal(oxc::Span::default()),
1807 - )
1808 - }
1809 - ExportDefaultDecl::Expression(e) => {
1810 - oxc::ExportDefaultDeclarationKind::from(self.convert_expression(e))
1811 - }
1812 - }
1813 - }
1814 -
1815 - fn convert_export_all_declaration(
1816 - &self,
1817 - decl: &ExportAllDeclaration,
1818 - ) -> oxc::ExportAllDeclaration<'a> {
1819 - let source = self
1820 - .builder
1821 - .string_literal(SPAN, self.atom(&decl.source.value), None);
1822 - let export_kind = match decl.export_kind.as_ref() {
1823 - Some(ExportKind::Type) => oxc::ImportOrExportKind::Type,
1824 - _ => oxc::ImportOrExportKind::Value,
1825 - };
1826 - self.builder.export_all_declaration(
1827 - SPAN,
1828 - None, // exported
1829 - source,
1830 - None::<oxc_allocator::Box<'a, oxc::WithClause<'a>>>,
1831 - export_kind,
1832 - )
1833 - }
1834 -
1835 - // ===== Operators =====
1836 -
1837 - fn convert_binary_operator(&self, op: &BinaryOperator) -> oxc_syntax::operator::BinaryOperator {
1838 - use oxc_syntax::operator::BinaryOperator as OxcBinOp;
1839 - match op {
1840 - BinaryOperator::Add => OxcBinOp::Addition,
1841 - BinaryOperator::Sub => OxcBinOp::Subtraction,
1842 - BinaryOperator::Mul => OxcBinOp::Multiplication,
1843 - BinaryOperator::Div => OxcBinOp::Division,
1844 - BinaryOperator::Rem => OxcBinOp::Remainder,
1845 - BinaryOperator::Exp => OxcBinOp::Exponential,
1846 - BinaryOperator::Eq => OxcBinOp::Equality,
1847 - BinaryOperator::StrictEq => OxcBinOp::StrictEquality,
1848 - BinaryOperator::Neq => OxcBinOp::Inequality,
1849 - BinaryOperator::StrictNeq => OxcBinOp::StrictInequality,
1850 - BinaryOperator::Lt => OxcBinOp::LessThan,
1851 - BinaryOperator::Lte => OxcBinOp::LessEqualThan,
1852 - BinaryOperator::Gt => OxcBinOp::GreaterThan,
1853 - BinaryOperator::Gte => OxcBinOp::GreaterEqualThan,
1854 - BinaryOperator::Shl => OxcBinOp::ShiftLeft,
1855 - BinaryOperator::Shr => OxcBinOp::ShiftRight,
1856 - BinaryOperator::UShr => OxcBinOp::ShiftRightZeroFill,
1857 - BinaryOperator::BitOr => OxcBinOp::BitwiseOR,
1858 - BinaryOperator::BitXor => OxcBinOp::BitwiseXOR,
1859 - BinaryOperator::BitAnd => OxcBinOp::BitwiseAnd,
1860 - BinaryOperator::In => OxcBinOp::In,
1861 - BinaryOperator::Instanceof => OxcBinOp::Instanceof,
1862 - BinaryOperator::Pipeline => OxcBinOp::BitwiseOR, // no pipeline in OXC
1863 - }
1864 - }
1865 -
1866 - fn convert_logical_operator(
1867 - &self,
1868 - op: &LogicalOperator,
1869 - ) -> oxc_syntax::operator::LogicalOperator {
1870 - use oxc_syntax::operator::LogicalOperator as OxcLogOp;
1871 - match op {
1872 - LogicalOperator::Or => OxcLogOp::Or,
1873 - LogicalOperator::And => OxcLogOp::And,
1874 - LogicalOperator::NullishCoalescing => OxcLogOp::Coalesce,
1875 - }
1876 - }
1877 -
1878 - fn convert_unary_operator(&self, op: &UnaryOperator) -> oxc_syntax::operator::UnaryOperator {
1879 - use oxc_syntax::operator::UnaryOperator as OxcUnOp;
1880 - match op {
1881 - UnaryOperator::Neg => OxcUnOp::UnaryNegation,
1882 - UnaryOperator::Plus => OxcUnOp::UnaryPlus,
1883 - UnaryOperator::Not => OxcUnOp::LogicalNot,
1884 - UnaryOperator::BitNot => OxcUnOp::BitwiseNot,
1885 - UnaryOperator::TypeOf => OxcUnOp::Typeof,
1886 - UnaryOperator::Void => OxcUnOp::Void,
1887 - UnaryOperator::Delete => OxcUnOp::Delete,
1888 - UnaryOperator::Throw => OxcUnOp::Void, // no throw-as-unary in OXC
1889 - }
1890 - }
1891 -
1892 - fn convert_update_operator(&self, op: &UpdateOperator) -> oxc_syntax::operator::UpdateOperator {
1893 - use oxc_syntax::operator::UpdateOperator as OxcUpOp;
1894 - match op {
1895 - UpdateOperator::Increment => OxcUpOp::Increment,
1896 - UpdateOperator::Decrement => OxcUpOp::Decrement,
1897 - }
1898 - }
1899 -
1900 - fn convert_assignment_operator(
1901 - &self,
1902 - op: &AssignmentOperator,
1903 - ) -> oxc_syntax::operator::AssignmentOperator {
1904 - use oxc_syntax::operator::AssignmentOperator as OxcAssOp;
1905 - match op {
1906 - AssignmentOperator::Assign => OxcAssOp::Assign,
1907 - AssignmentOperator::AddAssign => OxcAssOp::Addition,
1908 - AssignmentOperator::SubAssign => OxcAssOp::Subtraction,
1909 - AssignmentOperator::MulAssign => OxcAssOp::Multiplication,
1910 - AssignmentOperator::DivAssign => OxcAssOp::Division,
1911 - AssignmentOperator::RemAssign => OxcAssOp::Remainder,
1912 - AssignmentOperator::ExpAssign => OxcAssOp::Exponential,
1913 - AssignmentOperator::ShlAssign => OxcAssOp::ShiftLeft,
1914 - AssignmentOperator::ShrAssign => OxcAssOp::ShiftRight,
1915 - AssignmentOperator::UShrAssign => OxcAssOp::ShiftRightZeroFill,
1916 - AssignmentOperator::BitOrAssign => OxcAssOp::BitwiseOR,
1917 - AssignmentOperator::BitXorAssign => OxcAssOp::BitwiseXOR,
1918 - AssignmentOperator::BitAndAssign => OxcAssOp::BitwiseAnd,
1919 - AssignmentOperator::OrAssign => OxcAssOp::LogicalOr,
1920 - AssignmentOperator::AndAssign => OxcAssOp::LogicalAnd,
1921 - AssignmentOperator::NullishAssign => OxcAssOp::LogicalNullish,
1922 - }
1923 - }
1924 -
1925 - fn parse_regexp_flags(&self, flags_str: &str) -> oxc::RegExpFlags {
1926 - let mut flags = oxc::RegExpFlags::empty();
1927 - for ch in flags_str.chars() {
1928 - match ch {
1929 - 'd' => flags |= oxc::RegExpFlags::D,
1930 - 'g' => flags |= oxc::RegExpFlags::G,
1931 - 'i' => flags |= oxc::RegExpFlags::I,
1932 - 'm' => flags |= oxc::RegExpFlags::M,
1933 - 's' => flags |= oxc::RegExpFlags::S,
1934 - 'u' => flags |= oxc::RegExpFlags::U,
1935 - 'v' => flags |= oxc::RegExpFlags::V,
1936 - 'y' => flags |= oxc::RegExpFlags::Y,
1937 - _ => {}
1938 - }
1939 - }
1940 - flags
1941 - }
1942 -}
compiler/crates/react_compiler_oxc/src/convert_scope.rs deleted
-535
@@ -1,535 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use std::collections::HashMap;
7 -
8 -use indexmap::IndexMap;
9 -use oxc_ast::AstKind;
10 -use oxc_ast::ast::Program;
11 -use oxc_semantic::Semantic;
12 -use oxc_span::GetSpan;
13 -use oxc_syntax::symbol::SymbolFlags;
14 -use react_compiler_ast::scope::*;
15 -
16 -/// Convert OXC's semantic analysis into React Compiler's ScopeInfo.
17 -pub fn convert_scope_info(semantic: &Semantic, _program: &Program) -> ScopeInfo {
18 - let scoping = semantic.scoping();
19 - let nodes = semantic.nodes();
20 -
21 - let mut scopes: Vec<ScopeData> = Vec::new();
22 - let mut bindings: Vec<BindingData> = Vec::new();
23 - let mut node_to_scope: HashMap<u32, ScopeId> = HashMap::new();
24 - let mut node_to_scope_end: HashMap<u32, u32> = HashMap::new();
25 - // In OXC, span.start is used as node_id (OXC spans are unique).
26 - let mut node_id_to_scope: HashMap<u32, ScopeId> = HashMap::new();
27 - let mut ref_node_id_to_binding: IndexMap<u32, BindingId> = IndexMap::new();
28 -
29 - // Map OXC symbol IDs to our binding IDs
30 - let mut symbol_to_binding: HashMap<oxc_syntax::symbol::SymbolId, BindingId> = HashMap::new();
31 -
32 - // First pass: Create all bindings from symbols
33 - for symbol_id in scoping.symbol_ids() {
34 - let symbol_flags = scoping.symbol_flags(symbol_id);
35 - let name = scoping.symbol_name(symbol_id).to_string();
36 -
37 - let kind = get_binding_kind(symbol_flags, semantic, symbol_id);
38 -
39 - let (declaration_type, declaration_start) =
40 - get_declaration_info(semantic, symbol_id, &name);
41 -
42 - let import = if matches!(kind, BindingKind::Module) {
43 - get_import_data(semantic, symbol_id)
44 - } else {
45 - None
46 - };
47 -
48 - let binding_id = BindingId(bindings.len() as u32);
49 - symbol_to_binding.insert(symbol_id, binding_id);
50 -
51 - bindings.push(BindingData {
52 - id: binding_id,
53 - name,
54 - kind,
55 - scope: ScopeId(0), // Placeholder, filled in second pass
56 - declaration_type,
57 - declaration_start,
58 - declaration_node_id: declaration_start,
59 - import,
60 - });
61 - }
62 -
63 - // Second pass: Create all scopes and update binding scope references
64 - for scope_id in scoping.scope_descendants_from_root() {
65 - let scope_flags = scoping.scope_flags(scope_id);
66 - let parent = scoping.scope_parent_id(scope_id);
67 -
68 - let our_scope_id = ScopeId(scope_id.index() as u32);
69 -
70 - let kind = get_scope_kind(scope_flags, semantic, scope_id);
71 -
72 - // Collect bindings in this scope
73 - let mut scope_bindings: HashMap<String, BindingId> = HashMap::new();
74 - for symbol_id in scoping.iter_bindings_in(scope_id) {
75 - if let Some(&binding_id) = symbol_to_binding.get(&symbol_id) {
76 - let name = bindings[binding_id.0 as usize].name.clone();
77 - scope_bindings.insert(name, binding_id);
78 - bindings[binding_id.0 as usize].scope = our_scope_id;
79 - }
80 - }
81 -
82 - // Map the AST node that created this scope to the scope ID
83 - let node_id = scoping.get_node_id(scope_id);
84 - let node = nodes.get_node(node_id);
85 - let span = node.kind().span();
86 - let start = span.start;
87 - let end = span.end;
88 - // For function scopes inside object methods, also map the parent
89 - // ObjectProperty start so the compiler can look up the scope using the
90 - // ObjectMethod's start position (which matches the property start in Babel).
91 - if matches!(kind, ScopeKind::Function) {
92 - if let AstKind::Function(_) = node.kind() {
93 - let parent_node_id = nodes.parent_id(node_id);
94 - let parent_node = nodes.get_node(parent_node_id);
95 - match parent_node.kind() {
96 - AstKind::ObjectProperty(prop)
97 - if prop.method
98 - || matches!(
99 - prop.kind,
100 - oxc_ast::ast::PropertyKind::Get | oxc_ast::ast::PropertyKind::Set
101 - ) =>
102 - {
103 - let prop_start = parent_node.kind().span().start;
104 - if prop_start != start {
105 - node_to_scope.insert(prop_start, our_scope_id);
106 - node_to_scope_end.insert(prop_start, end);
107 - node_id_to_scope.insert(prop_start, our_scope_id);
108 - }
109 - }
110 - _ => {}
111 - }
112 - }
113 - }
114 -
115 - if end > start {
116 - node_to_scope.insert(start, our_scope_id);
117 - node_to_scope_end.insert(start, end);
118 - } else {
119 - node_to_scope.insert(start, our_scope_id);
120 - }
121 - node_id_to_scope.insert(start, our_scope_id);
122 -
123 - scopes.push(ScopeData {
124 - id: our_scope_id,
125 - parent: parent.map(|p| ScopeId(p.index() as u32)),
126 - kind,
127 - bindings: scope_bindings,
128 - });
129 - }
130 -
131 - // Third pass: Map all resolved references to bindings
132 - for symbol_id in scoping.symbol_ids() {
133 - if let Some(&binding_id) = symbol_to_binding.get(&symbol_id) {
134 - for &ref_id in scoping.get_resolved_reference_ids(symbol_id) {
135 - let reference = scoping.get_reference(ref_id);
136 - let ref_node = nodes.get_node(reference.node_id());
137 - let start = ref_node.kind().span().start;
138 - ref_node_id_to_binding.insert(start, binding_id);
139 - }
140 - }
141 - }
142 -
143 - // Also map declaration identifiers to their bindings
144 - for symbol_id in scoping.symbol_ids() {
145 - if let Some(&binding_id) = symbol_to_binding.get(&symbol_id) {
146 - if let Some(start) = bindings[binding_id.0 as usize].declaration_start {
147 - ref_node_id_to_binding.entry(start).or_insert(binding_id);
148 - }
149 - }
150 - }
151 -
152 - // Map `export default function Foo` — Babel treats the ExportDefaultDeclaration
153 - // as a reference to the function's binding. OXC doesn't create a reference for
154 - // the export itself, so we add one at the export statement's start position.
155 - for stmt in &_program.body {
156 - if let oxc_ast::ast::Statement::ExportDefaultDeclaration(export) = stmt {
157 - if let oxc_ast::ast::ExportDefaultDeclarationKind::FunctionDeclaration(func) =
158 - &export.declaration
159 - {
160 - if let Some(id) = &func.id {
161 - let name = id.name.as_str();
162 - if let Some(symbol_id) = id.symbol_id.get() {
163 - if let Some(&binding_id) = symbol_to_binding.get(&symbol_id) {
164 - let export_start = export.span().start;
165 - ref_node_id_to_binding
166 - .entry(export_start)
167 - .or_insert(binding_id);
168 - }
169 - } else {
170 - // Fallback: look up binding by name
171 - for (sym_id, &bind_id) in &symbol_to_binding {
172 - if scoping.symbol_name(*sym_id) == name {
173 - let export_start = export.span().start;
174 - ref_node_id_to_binding
175 - .entry(export_start)
176 - .or_insert(bind_id);
177 - break;
178 - }
179 - }
180 - }
181 - }
182 - }
183 - }
184 - }
185 -
186 - // Map `export default function Foo` — Babel treats the ExportDefaultDeclaration
187 - // as a reference to the function's binding. OXC doesn't create a reference for
188 - // the export itself, so we add one at the export statement's start position.
189 - for stmt in &_program.body {
190 - if let oxc_ast::ast::Statement::ExportDefaultDeclaration(export) = stmt {
191 - if let oxc_ast::ast::ExportDefaultDeclarationKind::FunctionDeclaration(func) =
192 - &export.declaration
193 - {
194 - if let Some(id) = &func.id {
195 - let name = id.name.as_str();
196 - if let Some(symbol_id) = id.symbol_id.get() {
197 - if let Some(&binding_id) = symbol_to_binding.get(&symbol_id) {
198 - ref_node_id_to_binding
199 - .entry(export.span().start)
200 - .or_insert(binding_id);
201 - }
202 - } else {
203 - // Fallback: look up binding by name
204 - for (sym_id, &bind_id) in &symbol_to_binding {
205 - if scoping.symbol_name(*sym_id) == name {
206 - ref_node_id_to_binding
207 - .entry(export.span().start)
208 - .or_insert(bind_id);
209 - break;
210 - }
211 - }
212 - }
213 - }
214 - }
215 - }
216 - }
217 -
218 - let program_scope = ScopeId(scoping.root_scope_id().index() as u32);
219 -
220 - ScopeInfo {
221 - scopes,
222 - bindings,
223 - node_to_scope,
224 - node_to_scope_end,
225 - reference_to_binding: IndexMap::new(),
226 - ref_node_id_to_binding,
227 - node_id_to_scope,
228 - program_scope,
229 - }
230 -}
231 -
232 -/// Map OXC ScopeFlags to our ScopeKind.
233 -fn get_scope_kind(
234 - flags: oxc_syntax::scope::ScopeFlags,
235 - semantic: &Semantic,
236 - scope_id: oxc_syntax::scope::ScopeId,
237 -) -> ScopeKind {
238 - if flags.contains(oxc_syntax::scope::ScopeFlags::Top) {
239 - return ScopeKind::Program;
240 - }
241 -
242 - if flags.intersects(oxc_syntax::scope::ScopeFlags::Function) {
243 - return ScopeKind::Function;
244 - }
245 -
246 - if flags.contains(oxc_syntax::scope::ScopeFlags::CatchClause) {
247 - return ScopeKind::Catch;
248 - }
249 -
250 - if flags.contains(oxc_syntax::scope::ScopeFlags::ClassStaticBlock) {
251 - return ScopeKind::Class;
252 - }
253 -
254 - // Check the AST node to determine if it's a for loop, class, or switch
255 - let node_id = semantic.scoping().get_node_id(scope_id);
256 - let node = semantic.nodes().get_node(node_id);
257 - match node.kind() {
258 - AstKind::ForStatement(_) | AstKind::ForInStatement(_) | AstKind::ForOfStatement(_) => {
259 - ScopeKind::For
260 - }
261 - AstKind::Class(_) => ScopeKind::Class,
262 - AstKind::SwitchStatement(_) => ScopeKind::Switch,
263 - _ => ScopeKind::Block,
264 - }
265 -}
266 -
267 -/// Map OXC SymbolFlags to our BindingKind.
268 -fn get_binding_kind(
269 - flags: SymbolFlags,
270 - semantic: &Semantic,
271 - symbol_id: oxc_syntax::symbol::SymbolId,
272 -) -> BindingKind {
273 - if flags.contains(SymbolFlags::Import) {
274 - return BindingKind::Module;
275 - }
276 -
277 - // Check the declaration node first — FormalParameter and CatchParameter
278 - // need to be detected before the FunctionScopedVariable flag check, because
279 - // OXC marks function parameters and catch parameters with FunctionScopedVariable.
280 - let decl_node = semantic.symbol_declaration(symbol_id);
281 - match decl_node.kind() {
282 - AstKind::FormalParameter(_) => return BindingKind::Param,
283 - AstKind::FormalParameterRest(_) => return BindingKind::Param,
284 - AstKind::CatchParameter(_) => return BindingKind::Let,
285 - AstKind::TSTypeAliasDeclaration(_) => return BindingKind::Local,
286 - AstKind::TSEnumDeclaration(_) => return BindingKind::Local,
287 - AstKind::TSModuleDeclaration(_) => return BindingKind::Local,
288 - AstKind::Function(_) => {
289 - if flags.contains(SymbolFlags::Function) {
290 - return BindingKind::Hoisted;
291 - }
292 - return BindingKind::Local;
293 - }
294 - AstKind::Class(_) => return BindingKind::Local,
295 - _ => {}
296 - }
297 -
298 - if flags.contains(SymbolFlags::FunctionScopedVariable) {
299 - return BindingKind::Var;
300 - }
301 -
302 - if flags.contains(SymbolFlags::BlockScopedVariable) {
303 - if flags.contains(SymbolFlags::ConstVariable) {
304 - return BindingKind::Const;
305 - } else {
306 - return BindingKind::Let;
307 - }
308 - }
309 -
310 - if flags.contains(SymbolFlags::Function) {
311 - BindingKind::Hoisted
312 - } else if flags.contains(SymbolFlags::Class) {
313 - BindingKind::Local
314 - } else {
315 - BindingKind::Unknown
316 - }
317 -}
318 -
319 -/// Get the declaration type string and start position for a binding.
320 -fn get_declaration_info(
321 - semantic: &Semantic,
322 - symbol_id: oxc_syntax::symbol::SymbolId,
323 - name: &str,
324 -) -> (String, Option<u32>) {
325 - let decl_node = semantic.symbol_declaration(symbol_id);
326 - let declaration_type = ast_kind_to_string(decl_node.kind());
327 - let declaration_start = find_binding_identifier_start(decl_node.kind(), name);
328 - (declaration_type, declaration_start)
329 -}
330 -
331 -/// Convert an AstKind to its Babel-equivalent string representation.
332 -fn ast_kind_to_string(kind: AstKind) -> String {
333 - match kind {
334 - AstKind::BindingIdentifier(_) => "BindingIdentifier",
335 - AstKind::VariableDeclarator(_) => "VariableDeclarator",
336 - AstKind::Function(f) => {
337 - if f.is_declaration() {
338 - "FunctionDeclaration"
339 - } else {
340 - "FunctionExpression"
341 - }
342 - }
343 - AstKind::Class(c) => {
344 - if c.is_declaration() {
345 - "ClassDeclaration"
346 - } else {
347 - "ClassExpression"
348 - }
349 - }
350 - AstKind::FormalParameter(_) => "FormalParameter",
351 - AstKind::FormalParameterRest(_) => "FormalParameter",
352 - AstKind::ImportSpecifier(_) => "ImportSpecifier",
353 - AstKind::ImportDefaultSpecifier(_) => "ImportDefaultSpecifier",
354 - AstKind::ImportNamespaceSpecifier(_) => "ImportNamespaceSpecifier",
355 - AstKind::CatchClause(_) => "CatchClause",
356 - AstKind::CatchParameter(_) => "CatchClause",
357 - AstKind::TSTypeAliasDeclaration(_) => "TSTypeAliasDeclaration",
358 - AstKind::TSEnumDeclaration(_) => "TSEnumDeclaration",
359 - AstKind::TSModuleDeclaration(_) => "TSModuleDeclaration",
360 - _ => "Unknown",
361 - }
362 - .to_string()
363 -}
364 -
365 -/// Find the binding identifier's start position within an AST node.
366 -fn find_binding_identifier_start(kind: AstKind, name: &str) -> Option<u32> {
367 - match kind {
368 - AstKind::BindingIdentifier(ident) => {
369 - if ident.name.as_str() == name {
370 - Some(ident.span.start)
371 - } else {
372 - None
373 - }
374 - }
375 - AstKind::VariableDeclarator(decl) => find_identifier_in_pattern(&decl.id, name),
376 - AstKind::Function(func) => func.id.as_ref().and_then(|id| {
377 - if id.name.as_str() == name {
378 - Some(id.span.start)
379 - } else {
380 - None
381 - }
382 - }),
383 - AstKind::Class(class) => class.id.as_ref().and_then(|id| {
384 - if id.name.as_str() == name {
385 - Some(id.span.start)
386 - } else {
387 - None
388 - }
389 - }),
390 - AstKind::FormalParameter(param) => find_identifier_in_pattern(&param.pattern, name),
391 - AstKind::FormalParameterRest(rest) => find_identifier_in_pattern(&rest.rest.argument, name),
392 - AstKind::ImportSpecifier(spec) => Some(spec.local.span.start),
393 - AstKind::ImportDefaultSpecifier(spec) => Some(spec.local.span.start),
394 - AstKind::ImportNamespaceSpecifier(spec) => Some(spec.local.span.start),
395 - AstKind::CatchClause(catch) => catch
396 - .param
397 - .as_ref()
398 - .and_then(|p| find_identifier_in_pattern(&p.pattern, name)),
399 - AstKind::CatchParameter(param) => find_identifier_in_pattern(&param.pattern, name),
400 - AstKind::TSTypeAliasDeclaration(decl) => {
401 - if decl.id.name.as_str() == name {
402 - Some(decl.id.span.start)
403 - } else {
404 - None
405 - }
406 - }
407 - AstKind::TSEnumDeclaration(decl) => {
408 - if decl.id.name.as_str() == name {
409 - Some(decl.id.span.start)
410 - } else {
411 - None
412 - }
413 - }
414 - AstKind::TSModuleDeclaration(decl) => match &decl.id {
415 - oxc_ast::ast::TSModuleDeclarationName::Identifier(id) => {
416 - if id.name.as_str() == name {
417 - Some(id.span.start)
418 - } else {
419 - None
420 - }
421 - }
422 - _ => None,
423 - },
424 - _ => None,
425 - }
426 -}
427 -
428 -/// Recursively find a binding identifier within a binding pattern.
429 -fn find_identifier_in_pattern(pattern: &oxc_ast::ast::BindingPattern, name: &str) -> Option<u32> {
430 - use oxc_ast::ast::BindingPattern;
431 -
432 - match pattern {
433 - BindingPattern::BindingIdentifier(ident) => {
434 - if ident.name.as_str() == name {
435 - Some(ident.span.start)
436 - } else {
437 - None
438 - }
439 - }
440 - BindingPattern::ObjectPattern(obj) => {
441 - for prop in &obj.properties {
442 - if let Some(start) = find_identifier_in_pattern(&prop.value, name) {
443 - return Some(start);
444 - }
445 - }
446 - if let Some(rest) = &obj.rest {
447 - if let Some(start) = find_identifier_in_pattern(&rest.argument, name) {
448 - return Some(start);
449 - }
450 - }
451 - None
452 - }
453 - BindingPattern::ArrayPattern(arr) => {
454 - for element in arr.elements.iter().flatten() {
455 - if let Some(start) = find_identifier_in_pattern(element, name) {
456 - return Some(start);
457 - }
458 - }
459 - if let Some(rest) = &arr.rest {
460 - if let Some(start) = find_identifier_in_pattern(&rest.argument, name) {
461 - return Some(start);
462 - }
463 - }
464 - None
465 - }
466 - BindingPattern::AssignmentPattern(assign) => find_identifier_in_pattern(&assign.left, name),
467 - }
468 -}
469 -
470 -/// Extract import data for a module binding.
471 -fn get_import_data(
472 - semantic: &Semantic,
473 - symbol_id: oxc_syntax::symbol::SymbolId,
474 -) -> Option<ImportBindingData> {
475 - let decl_node = semantic.symbol_declaration(symbol_id);
476 -
477 - match decl_node.kind() {
478 - AstKind::ImportDefaultSpecifier(_) => {
479 - let import_decl = find_import_declaration(semantic, decl_node.id())?;
480 - Some(ImportBindingData {
481 - source: import_decl.source.value.to_string(),
482 - kind: ImportBindingKind::Default,
483 - imported: None,
484 - })
485 - }
486 - AstKind::ImportNamespaceSpecifier(_) => {
487 - let import_decl = find_import_declaration(semantic, decl_node.id())?;
488 - Some(ImportBindingData {
489 - source: import_decl.source.value.to_string(),
490 - kind: ImportBindingKind::Namespace,
491 - imported: None,
492 - })
493 - }
494 - AstKind::ImportSpecifier(spec) => {
495 - let import_decl = find_import_declaration(semantic, decl_node.id())?;
496 - let imported_name = match &spec.imported {
497 - oxc_ast::ast::ModuleExportName::IdentifierName(ident) => ident.name.to_string(),
498 - oxc_ast::ast::ModuleExportName::IdentifierReference(ident) => {
499 - ident.name.to_string()
500 - }
501 - oxc_ast::ast::ModuleExportName::StringLiteral(lit) => lit.value.to_string(),
502 - };
503 - Some(ImportBindingData {
504 - source: import_decl.source.value.to_string(),
505 - kind: ImportBindingKind::Named,
506 - imported: Some(imported_name),
507 - })
508 - }
509 - _ => None,
510 - }
511 -}
512 -
513 -/// Find the ImportDeclaration node that contains the given import specifier.
514 -fn find_import_declaration<'a>(
515 - semantic: &'a Semantic,
516 - specifier_node_id: oxc_semantic::NodeId,
517 -) -> Option<&'a oxc_ast::ast::ImportDeclaration<'a>> {
518 - let mut current_id = specifier_node_id;
519 - // Walk up the parent chain (max 10 levels to avoid infinite loop)
520 - for _ in 0..10 {
521 - let parent_id = semantic.nodes().parent_id(current_id);
522 - if parent_id == current_id {
523 - // Root node, no more parents
524 - return None;
525 - }
526 - let parent_node = semantic.nodes().get_node(parent_id);
527 -
528 - if let AstKind::ImportDeclaration(decl) = parent_node.kind() {
529 - return Some(decl);
530 - }
531 -
532 - current_id = parent_id;
533 - }
534 - None
535 -}
compiler/crates/react_compiler_oxc/src/diagnostics.rs deleted
-93
@@ -1,93 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use oxc_diagnostics::OxcDiagnostic;
7 -use react_compiler::entrypoint::compile_result::{
8 - CompileResult, CompilerErrorDetailInfo, LoggerEvent,
9 -};
10 -
11 -/// Converts a CompileResult into OXC diagnostics for display
12 -pub fn compile_result_to_diagnostics(result: &CompileResult) -> Vec<OxcDiagnostic> {
13 - let mut diagnostics = Vec::new();
14 -
15 - match result {
16 - CompileResult::Success { events, .. } => {
17 - // Process logger events from successful compilation
18 - for event in events {
19 - if let Some(diag) = event_to_diagnostic(event) {
20 - diagnostics.push(diag);
21 - }
22 - }
23 - }
24 - CompileResult::Error {
25 - error,
26 - events,
27 - ..
28 - } => {
29 - // Add the main error
30 - diagnostics.push(error_info_to_diagnostic(error));
31 -
32 - // Process logger events from failed compilation
33 - for event in events {
34 - if let Some(diag) = event_to_diagnostic(event) {
35 - diagnostics.push(diag);
36 - }
37 - }
38 - }
39 - }
40 -
41 - diagnostics
42 -}
43 -
44 -fn error_info_to_diagnostic(error: &react_compiler::entrypoint::compile_result::CompilerErrorInfo) -> OxcDiagnostic {
45 - let message = format!("[ReactCompiler] {}", error.reason);
46 - let mut diag = OxcDiagnostic::error(message);
47 -
48 - if let Some(description) = &error.description {
49 - diag = diag.with_help(description.clone());
50 - }
51 -
52 - diag
53 -}
54 -
55 -fn error_detail_to_diagnostic(detail: &CompilerErrorDetailInfo, is_error: bool) -> OxcDiagnostic {
56 - let message = if let Some(description) = &detail.description {
57 - format!(
58 - "[ReactCompiler] {}: {}. {}",
59 - detail.category, detail.reason, description
60 - )
61 - } else {
62 - format!("[ReactCompiler] {}: {}", detail.category, detail.reason)
63 - };
64 -
65 - if is_error {
66 - OxcDiagnostic::error(message)
67 - } else {
68 - OxcDiagnostic::warn(message)
69 - }
70 -}
71 -
72 -fn event_to_diagnostic(event: &LoggerEvent) -> Option<OxcDiagnostic> {
73 - match event {
74 - LoggerEvent::CompileSuccess { .. } => None,
75 - LoggerEvent::CompileSkip { .. } => None,
76 - LoggerEvent::CompileError { detail, .. }
77 - | LoggerEvent::CompileErrorWithLoc { detail, .. } => {
78 - Some(error_detail_to_diagnostic(detail, false))
79 - }
80 - LoggerEvent::CompileUnexpectedThrow { data, .. } => {
81 - Some(OxcDiagnostic::error(format!(
82 - "[ReactCompiler] Unexpected error: {}",
83 - data
84 - )))
85 - }
86 - LoggerEvent::PipelineError { data, .. } => {
87 - Some(OxcDiagnostic::error(format!(
88 - "[ReactCompiler] Pipeline error: {}",
89 - data
90 - )))
91 - }
92 - }
93 -}
compiler/crates/react_compiler_oxc/src/lib.rs deleted
-206
@@ -1,206 +0,0 @@
1 -pub mod apply_renames;
2 -pub mod convert_ast;
3 -pub mod convert_ast_reverse;
4 -pub mod convert_scope;
5 -pub mod diagnostics;
6 -pub mod prefilter;
7 -
8 -use std::collections::HashMap;
9 -
10 -use apply_renames::build_rename_plan;
11 -use convert_ast::convert_program;
12 -use convert_scope::convert_scope_info;
13 -use diagnostics::compile_result_to_diagnostics;
14 -use prefilter::has_react_like_functions;
15 -use react_compiler::entrypoint::compile_result::LoggerEvent;
16 -use react_compiler::entrypoint::plugin_options::PluginOptions;
17 -
18 -/// Result of compiling a program via the OXC frontend.
19 -pub struct TransformResult {
20 - /// The compiled program as a react_compiler_ast File (None if no changes needed).
21 - pub file: Option<react_compiler_ast::File>,
22 - pub diagnostics: Vec<oxc_diagnostics::OxcDiagnostic>,
23 - pub events: Vec<LoggerEvent>,
24 - /// Pre-computed rename plan: maps source positions (span.start) to new
25 - /// identifier names. Built from the compiler's binding renames and the
26 - /// original scope info. Applied during `emit()` to fix references in
27 - /// uncompiled sibling functions.
28 - pub rename_plan: HashMap<u32, String>,
29 -}
30 -
31 -/// Result of linting a program via the OXC frontend.
32 -pub struct LintResult {
33 - pub diagnostics: Vec<oxc_diagnostics::OxcDiagnostic>,
34 -}
35 -
36 -/// Primary transform API — accepts pre-parsed OXC AST + semantic.
37 -pub fn transform(
38 - program: &oxc_ast::ast::Program,
39 - semantic: &oxc_semantic::Semantic,
40 - source_text: &str,
41 - options: PluginOptions,
42 -) -> TransformResult {
43 - // Prefilter: skip files without React-like functions (unless compilationMode == "all")
44 - if options.compilation_mode != "all" && !has_react_like_functions(program) {
45 - return TransformResult {
46 - file: None,
47 - diagnostics: vec![],
48 - events: vec![],
49 - rename_plan: HashMap::new(),
50 - };
51 - }
52 -
53 - // Convert OXC AST to react_compiler_ast
54 - let file = convert_program(program, source_text);
55 -
56 - // Convert OXC semantic to ScopeInfo
57 - let scope_info = convert_scope_info(semantic, program);
58 -
59 - // Run the compiler
60 - let result =
61 - react_compiler::entrypoint::program::compile_program(file, scope_info.clone(), options);
62 -
63 - let diagnostics = compile_result_to_diagnostics(&result);
64 - let (program_ast, events, renames) = match result {
65 - react_compiler::entrypoint::compile_result::CompileResult::Success {
66 - ast,
67 - events,
68 - renames,
69 - ..
70 - } => (ast, events, renames),
71 - react_compiler::entrypoint::compile_result::CompileResult::Error {
72 - events, ..
73 - } => (None, events, Vec::new()),
74 - };
75 -
76 - // Build the rename plan from the original scope info + compiler renames.
77 - // This maps source positions to new identifier names for uncompiled code.
78 - let rename_plan = build_rename_plan(&scope_info, &renames);
79 -
80 - TransformResult {
81 - file: program_ast,
82 - diagnostics,
83 - events,
84 - rename_plan,
85 - }
86 -}
87 -
88 -/// Convenience wrapper — parses source text, runs semantic analysis, then transforms.
89 -pub fn transform_source(
90 - source_text: &str,
91 - source_type: oxc_span::SourceType,
92 - options: PluginOptions,
93 -) -> TransformResult {
94 - let allocator = oxc_allocator::Allocator::default();
95 - let parsed = oxc_parser::Parser::new(&allocator, source_text, source_type).parse();
96 -
97 - let semantic = oxc_semantic::SemanticBuilder::new()
98 - .build(&parsed.program)
99 - .semantic;
100 -
101 - transform(&parsed.program, &semantic, source_text, options)
102 -}
103 -
104 -/// Lint API — accepts pre-parsed OXC AST + semantic.
105 -/// Same as transform but only collects diagnostics, no AST output.
106 -pub fn lint(
107 - program: &oxc_ast::ast::Program,
108 - semantic: &oxc_semantic::Semantic,
109 - source_text: &str,
110 - options: PluginOptions,
111 -) -> LintResult {
112 - let mut opts = options;
113 - opts.no_emit = true;
114 -
115 - let result = transform(program, semantic, source_text, opts);
116 - LintResult {
117 - diagnostics: result.diagnostics,
118 - }
119 -}
120 -
121 -/// Emit a react_compiler_ast::File to a string via OXC codegen.
122 -/// Converts the File to an OXC Program, then uses oxc_codegen to emit.
123 -///
124 -/// If `source_text` is provided, comments from the original source will be
125 -/// preserved in the output by re-parsing the source to extract comments and
126 -/// injecting them into the OXC program before codegen.
127 -///
128 -/// If `rename_plan` is non-empty, binding renames are applied to the OXC
129 -/// program before emission. This fixes references in uncompiled sibling
130 -/// functions when the compiler renames a shared binding.
131 -pub fn emit(
132 - file: &react_compiler_ast::File,
133 - allocator: &oxc_allocator::Allocator,
134 - source_text: Option<&str>,
135 - rename_plan: &HashMap<u32, String>,
136 -) -> String {
137 - let mut program = if let Some(source) = source_text {
138 - convert_ast_reverse::convert_program_to_oxc_with_source(file, allocator, source)
139 - } else {
140 - convert_ast_reverse::convert_program_to_oxc(file, allocator)
141 - };
142 -
143 - if let Some(source) = source_text {
144 - // Re-parse the original source to extract comments.
145 - // We use a separate allocator for the parse since we only need the comments.
146 - let comment_allocator = oxc_allocator::Allocator::default();
147 - // Parse as TSX to handle maximum syntax variety
148 - let source_type = oxc_span::SourceType::tsx();
149 - let parsed =
150 - oxc_parser::Parser::new(&comment_allocator, source, source_type).parse();
151 -
152 - // Collect the span starts of top-level statements in the compiled
153 - // program. Only comments attached to these positions should be
154 - // preserved — comments inside function bodies would have
155 - // `attached_to` values that don't match any top-level statement.
156 - let mut top_level_starts = std::collections::HashSet::new();
157 - top_level_starts.insert(0u32); // position 0 for comments at the very start
158 - for stmt in &program.body {
159 - use oxc_span::GetSpan;
160 - let start = stmt.span().start;
161 - if start > 0 {
162 - top_level_starts.insert(start);
163 - }
164 - }
165 -
166 - // Copy only comments attached to top-level statements.
167 - let mut comments = oxc_allocator::Vec::with_capacity_in(
168 - parsed.program.comments.len(),
169 - allocator,
170 - );
171 - for comment in &parsed.program.comments {
172 - if top_level_starts.contains(&comment.attached_to) {
173 - comments.push(*comment);
174 - }
175 - }
176 - program.comments = comments;
177 -
178 - // Set the source_text so the codegen can extract comment content
179 - // from the original source spans.
180 - // We copy the source into the allocator to guarantee the lifetime.
181 - let source_in_alloc =
182 - oxc_allocator::StringBuilder::from_str_in(source, allocator);
183 - program.source_text = source_in_alloc.into_str();
184 - }
185 -
186 - // Apply binding renames to fix references in uncompiled sibling functions
187 - apply_renames::apply_renames(&mut program, rename_plan, allocator);
188 -
189 - oxc_codegen::Codegen::new().build(&program).code
190 -}
191 -
192 -/// Convenience wrapper — parses source text, runs semantic analysis, then lints.
193 -pub fn lint_source(
194 - source_text: &str,
195 - source_type: oxc_span::SourceType,
196 - options: PluginOptions,
197 -) -> LintResult {
198 - let allocator = oxc_allocator::Allocator::default();
199 - let parsed = oxc_parser::Parser::new(&allocator, source_text, source_type).parse();
200 -
201 - let semantic = oxc_semantic::SemanticBuilder::new()
202 - .build(&parsed.program)
203 - .semantic;
204 -
205 - lint(&parsed.program, &semantic, source_text, options)
206 -}
compiler/crates/react_compiler_oxc/src/prefilter.rs deleted
-149
@@ -1,149 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use oxc_ast::ast::{
7 - AssignmentTarget, Function, Program, VariableDeclarator,
8 -};
9 -use oxc_ast_visit::Visit;
10 -
11 -/// Checks if a program contains React-like functions (components or hooks).
12 -///
13 -/// A React-like function is one whose name:
14 -/// - Starts with an uppercase letter (component convention)
15 -/// - Matches the pattern `use[A-Z0-9]` (hook convention)
16 -pub fn has_react_like_functions(program: &Program) -> bool {
17 - let mut visitor = ReactLikeVisitor {
18 - found: false,
19 - current_name: None,
20 - };
21 - visitor.visit_program(program);
22 - visitor.found
23 -}
24 -
25 -use react_compiler_hir::environment::is_react_like_name;
26 -
27 -struct ReactLikeVisitor {
28 - found: bool,
29 - current_name: Option<String>,
30 -}
31 -
32 -impl<'a> Visit<'a> for ReactLikeVisitor {
33 - fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) {
34 - if self.found {
35 - return;
36 - }
37 -
38 - // Extract name from the binding identifier
39 - let name = match &decl.id {
40 - oxc_ast::ast::BindingPattern::BindingIdentifier(ident) => {
41 - Some(ident.name.to_string())
42 - }
43 - _ => None,
44 - };
45 -
46 - let prev_name = self.current_name.take();
47 - self.current_name = name;
48 -
49 - // Visit the initializer with the name in scope
50 - if let Some(init) = &decl.init {
51 - self.visit_expression(init);
52 - }
53 -
54 - self.current_name = prev_name;
55 - }
56 -
57 - fn visit_assignment_expression(
58 - &mut self,
59 - expr: &oxc_ast::ast::AssignmentExpression<'a>,
60 - ) {
61 - if self.found {
62 - return;
63 - }
64 -
65 - let name = match &expr.left {
66 - AssignmentTarget::AssignmentTargetIdentifier(ident) => {
67 - Some(ident.name.to_string())
68 - }
69 - _ => None,
70 - };
71 -
72 - let prev_name = self.current_name.take();
73 - self.current_name = name;
74 -
75 - self.visit_expression(&expr.right);
76 -
77 - self.current_name = prev_name;
78 - }
79 -
80 - fn visit_function(&mut self, func: &Function<'a>, _flags: oxc_semantic::ScopeFlags) {
81 - if self.found {
82 - return;
83 - }
84 -
85 - // Check explicit function name
86 - if let Some(id) = &func.id {
87 - if is_react_like_name(&id.name) {
88 - self.found = true;
89 - return;
90 - }
91 - }
92 -
93 - // Check inferred name from parent context
94 - if func.id.is_none() {
95 - if let Some(name) = &self.current_name {
96 - if is_react_like_name(name) {
97 - self.found = true;
98 - return;
99 - }
100 - }
101 - }
102 -
103 - // Don't traverse into the function body
104 - }
105 -
106 - fn visit_arrow_function_expression(
107 - &mut self,
108 - _expr: &oxc_ast::ast::ArrowFunctionExpression<'a>,
109 - ) {
110 - if self.found {
111 - return;
112 - }
113 -
114 - if let Some(name) = &self.current_name {
115 - if is_react_like_name(name) {
116 - self.found = true;
117 - return;
118 - }
119 - }
120 -
121 - // Don't traverse into the function body
122 - }
123 -
124 - fn visit_class(&mut self, _class: &oxc_ast::ast::Class<'a>) {
125 - // Skip class bodies entirely
126 - }
127 -}
128 -
129 -#[cfg(test)]
130 -mod tests {
131 - use super::*;
132 -
133 - #[test]
134 - fn test_is_react_like_name() {
135 - assert!(is_react_like_name("Component"));
136 - assert!(is_react_like_name("MyComponent"));
137 - assert!(is_react_like_name("A"));
138 - assert!(is_react_like_name("useState"));
139 - assert!(is_react_like_name("useEffect"));
140 - assert!(is_react_like_name("use0"));
141 -
142 - assert!(!is_react_like_name("component"));
143 - assert!(!is_react_like_name("myFunction"));
144 - assert!(!is_react_like_name("use"));
145 - assert!(!is_react_like_name("user"));
146 - assert!(!is_react_like_name("useful"));
147 - assert!(!is_react_like_name(""));
148 - }
149 -}
compiler/crates/react_compiler_reactive_scopes/src/assert_scope_instructions_within_scopes.rs
+13 -9
@@ -11,18 +11,19 @@
11 use std::collections::HashSet;
12
13 use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
14 -use react_compiler_hir::{
15 - EvaluationOrder, Place, ReactiveFunction, ReactiveScopeBlock, ScopeId,
16 -};
14 use react_compiler_hir::environment::Environment;
15 +use react_compiler_hir::{EvaluationOrder, Place, ReactiveFunction, ReactiveScopeBlock, ScopeId};
16
19 -use crate::visitors::{visit_reactive_function, ReactiveFunctionVisitor};
17 +use crate::visitors::{ReactiveFunctionVisitor, visit_reactive_function};
18
19 /// Assert that scope instructions are within their scopes.
20 /// Two-pass visitor:
21 /// 1. Collect all scope IDs
22 /// 2. Check that places referencing those scopes are within active scope blocks
25 -pub fn assert_scope_instructions_within_scopes(func: &ReactiveFunction, env: &Environment) -> Result<(), CompilerDiagnostic> {
23 +pub fn assert_scope_instructions_within_scopes(
24 + func: &ReactiveFunction,
25 + env: &Environment,
26 +) -> Result<(), CompilerDiagnostic> {
27 // Pass 1: Collect all scope IDs
28 let mut existing_scopes: HashSet<ScopeId> = HashSet::new();
29 let find_visitor = FindAllScopesVisitor { env };
@@ -53,7 +54,9 @@ struct FindAllScopesVisitor<'a> {
54 impl<'a> ReactiveFunctionVisitor for FindAllScopesVisitor<'a> {
55 type State = HashSet<ScopeId>;
56
56 - fn env(&self) -> &Environment { self.env }
57 + fn env(&self) -> &Environment {
58 + self.env
59 + }
60
61 fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut HashSet<ScopeId>) {
62 self.traverse_scope(scope, state);
@@ -78,7 +81,9 @@ struct CheckInstructionsAgainstScopesVisitor<'a> {
81 impl<'a> ReactiveFunctionVisitor for CheckInstructionsAgainstScopesVisitor<'a> {
82 type State = CheckState;
83
81 - fn env(&self) -> &Environment { self.env }
84 + fn env(&self) -> &Environment {
85 + self.env
86 + }
87
88 fn visit_place(&self, id: EvaluationOrder, place: &Place, state: &mut CheckState) {
89 // getPlaceScope: check if the place's identifier has a scope that is active at this id
@@ -86,8 +91,7 @@ impl<'a> ReactiveFunctionVisitor for CheckInstructionsAgainstScopesVisitor<'a> {
91 if let Some(scope_id) = identifier.scope {
92 let scope = &self.env.scopes[scope_id.0 as usize];
93 // isScopeActive: id >= scope.range.start && id < scope.range.end
89 - let is_active_at_id =
90 - id >= scope.range.start && id < scope.range.end;
94 + let is_active_at_id = id >= scope.range.start && id < scope.range.end;
95 if is_active_at_id
96 && state.existing_scopes.contains(&scope_id)
97 && !state.active_scopes.contains(&scope_id)
compiler/crates/react_compiler_reactive_scopes/src/assert_well_formed_break_targets.rs
+5 -7
@@ -14,7 +14,7 @@ use react_compiler_hir::{
14 environment::Environment,
15 };
16
17 -use crate::visitors::{visit_reactive_function, ReactiveFunctionVisitor};
17 +use crate::visitors::{ReactiveFunctionVisitor, visit_reactive_function};
18
19 /// Assert that all break/continue targets reference existent labels.
20 pub fn assert_well_formed_break_targets(func: &ReactiveFunction, env: &Environment) {
@@ -30,13 +30,11 @@ struct Visitor<'a> {
30 impl<'a> ReactiveFunctionVisitor for Visitor<'a> {
31 type State = HashSet<BlockId>;
32
33 - fn env(&self) -> &Environment { self.env }
33 + fn env(&self) -> &Environment {
34 + self.env
35 + }
36
35 - fn visit_terminal(
36 - &self,
37 - stmt: &ReactiveTerminalStatement,
38 - seen_labels: &mut HashSet<BlockId>,
39 - ) {
37 + fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, seen_labels: &mut HashSet<BlockId>) {
38 if let Some(label) = &stmt.label {
39 seen_labels.insert(label.id);
40 }
compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs
+759 -768
@@ -9,19 +9,28 @@
9
10 use std::collections::HashSet;
11
12 -use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation};
12 +use react_compiler_diagnostics::{
13 + CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,
14 +};
15 use react_compiler_hir::environment::Environment;
16 use react_compiler_hir::{
17 BasicBlock, BlockId, EvaluationOrder, GotoVariant, HirFunction, InstructionValue, Place,
16 - ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveLabel, ReactiveStatement,
17 - ReactiveTerminal, ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveScopeBlock,
18 - PrunedReactiveScopeBlock, ReactiveSwitchCase, ReactiveValue, Terminal,
18 + PrunedReactiveScopeBlock, ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveLabel,
19 + ReactiveScopeBlock, ReactiveStatement, ReactiveSwitchCase, ReactiveTerminal,
20 + ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveValue, Terminal,
21 };
22
23 /// Convert the HIR CFG into a tree-structured ReactiveFunction.
22 -pub fn build_reactive_function(hir: &HirFunction, env: &Environment) -> Result<ReactiveFunction, CompilerDiagnostic> {
24 +pub fn build_reactive_function(
25 + hir: &HirFunction,
26 + env: &Environment,
27 +) -> Result<ReactiveFunction, CompilerDiagnostic> {
28 let mut ctx = Context::new(hir);
24 - let mut driver = Driver { cx: &mut ctx, hir, env };
29 + let mut driver = Driver {
30 + cx: &mut ctx,
31 + hir,
32 + env,
33 + };
34
35 let entry_block_id = hir.body.entry;
36 let mut body = Vec::new();
@@ -147,11 +156,13 @@ impl<'a> Context<'a> {
156 "if" => ControlFlowTarget::If { block, id },
157 "switch" => ControlFlowTarget::Switch { block, id },
158 "case" => ControlFlowTarget::Case { block, id },
150 - _ => return Err(CompilerDiagnostic::new(
151 - ErrorCategory::Invariant,
152 - format!("Unknown target type: {}", target_type),
153 - None,
154 - )),
159 + _ => {
160 + return Err(CompilerDiagnostic::new(
161 + ErrorCategory::Invariant,
162 + format!("Unknown target type: {}", target_type),
163 + None,
164 + ));
165 + }
166 };
167 self.control_flow_stack.push(target);
168 Ok(id)
@@ -170,7 +181,10 @@ impl<'a> Context<'a> {
181 if self.scheduled.contains(&continue_block) {
182 return Err(CompilerDiagnostic::new(
183 ErrorCategory::Invariant,
173 - format!("Continue block is already scheduled: bb{}", continue_block.0),
184 + format!(
185 + "Continue block is already scheduled: bb{}",
186 + continue_block.0
187 + ),
188 None,
189 ));
190 }
@@ -270,10 +284,7 @@ impl<'a> Context<'a> {
284 ))
285 }
286
273 - fn get_continue_target(
274 - &self,
275 - block: BlockId,
276 - ) -> Option<(BlockId, ReactiveTerminalTargetKind)> {
287 + fn get_continue_target(&self, block: BlockId) -> Option<(BlockId, ReactiveTerminalTargetKind)> {
288 let mut has_preceding_loop = false;
289 for i in (0..self.control_flow_stack.len()).rev() {
290 let target = &self.control_flow_stack[i];
@@ -318,771 +329,770 @@ impl<'a, 'b> Driver<'a, 'b> {
329 Ok(block_value)
330 }
331
321 - fn visit_block(&mut self, mut block_id: BlockId, block_value: &mut ReactiveBlock) -> Result<(), CompilerDiagnostic> {
332 + fn visit_block(
333 + &mut self,
334 + mut block_id: BlockId,
335 + block_value: &mut ReactiveBlock,
336 + ) -> Result<(), CompilerDiagnostic> {
337 // Use a loop to avoid deep recursion for fallthrough chains.
338 // Each terminal that would tail-call visit_block(fallthrough, block_value)
339 // instead sets next_block and continues the loop.
340 loop {
326 - // Extract data from block before any mutable operations
327 - let block = &self.hir.body.blocks[&block_id];
328 - let block_id_val = block.id;
329 - let instructions: Vec<_> = block.instructions.clone();
330 - let terminal = block.terminal.clone();
341 + // Extract data from block before any mutable operations
342 + let block = &self.hir.body.blocks[&block_id];
343 + let block_id_val = block.id;
344 + let instructions: Vec<_> = block.instructions.clone();
345 + let terminal = block.terminal.clone();
346
332 - if !self.cx.emitted.insert(block_id_val) {
333 - return Err(CompilerDiagnostic::new(
334 - ErrorCategory::Invariant,
335 - format!("Block bb{} was already emitted", block_id_val.0),
336 - None,
337 - ));
338 - }
339 -
340 - // Emit instructions
341 - for instr_id in &instructions {
342 - let instr = &self.hir.instructions[instr_id.0 as usize];
343 - block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {
344 - id: instr.id,
345 - lvalue: Some(instr.lvalue.clone()),
346 - value: ReactiveValue::Instruction(instr.value.clone()),
347 - effects: instr.effects.clone(),
348 - loc: instr.loc,
349 - }));
350 - }
347 + if !self.cx.emitted.insert(block_id_val) {
348 + return Err(CompilerDiagnostic::new(
349 + ErrorCategory::Invariant,
350 + format!("Block bb{} was already emitted", block_id_val.0),
351 + None,
352 + ));
353 + }
354
352 - // Process terminal
353 - let mut schedule_ids: Vec<u32> = Vec::new();
354 - let mut next_block: Option<BlockId> = None;
355 + // Emit instructions
356 + for instr_id in &instructions {
357 + let instr = &self.hir.instructions[instr_id.0 as usize];
358 + block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {
359 + id: instr.id,
360 + lvalue: Some(instr.lvalue.clone()),
361 + value: ReactiveValue::Instruction(instr.value.clone()),
362 + effects: instr.effects.clone(),
363 + loc: instr.loc,
364 + }));
365 + }
366
356 - match &terminal {
357 - Terminal::If {
358 - test,
359 - consequent,
360 - alternate,
361 - fallthrough,
362 - id,
363 - loc,
364 - } => {
365 - // TS: reachable(fallthrough) && !isScheduled(fallthrough)
366 - let fallthrough_id = if self.cx.reachable(*fallthrough)
367 - && !self.cx.is_scheduled(*fallthrough)
368 - {
369 - Some(*fallthrough)
370 - } else {
371 - None
372 - };
373 - // TS: alternate !== fallthrough ? alternate : null
374 - let alternate_id = if *alternate != *fallthrough {
375 - Some(*alternate)
376 - } else {
377 - None
378 - };
367 + // Process terminal
368 + let mut schedule_ids: Vec<u32> = Vec::new();
369 + let mut next_block: Option<BlockId> = None;
370
380 - if let Some(ft) = fallthrough_id {
381 - schedule_ids.push(self.cx.schedule(ft, "if")?);
382 - }
371 + match &terminal {
372 + Terminal::If {
373 + test,
374 + consequent,
375 + alternate,
376 + fallthrough,
377 + id,
378 + loc,
379 + } => {
380 + // TS: reachable(fallthrough) && !isScheduled(fallthrough)
381 + let fallthrough_id =
382 + if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
383 + Some(*fallthrough)
384 + } else {
385 + None
386 + };
387 + // TS: alternate !== fallthrough ? alternate : null
388 + let alternate_id = if *alternate != *fallthrough {
389 + Some(*alternate)
390 + } else {
391 + None
392 + };
393
384 - let consequent_block = if self.cx.is_scheduled(*consequent) {
385 - return Err(CompilerDiagnostic::new(
386 - ErrorCategory::Invariant,
387 - format!("Unexpected 'if' where consequent is already scheduled (bb{})", consequent.0),
388 - None,
389 - ));
390 - } else {
391 - self.traverse_block(*consequent)?
392 - };
394 + if let Some(ft) = fallthrough_id {
395 + schedule_ids.push(self.cx.schedule(ft, "if")?);
396 + }
397
394 - let alternate_block = if let Some(alt) = alternate_id {
395 - if self.cx.is_scheduled(alt) {
398 + let consequent_block = if self.cx.is_scheduled(*consequent) {
399 return Err(CompilerDiagnostic::new(
400 ErrorCategory::Invariant,
398 - format!("Unexpected 'if' where the alternate is already scheduled (bb{})", alt.0),
401 + format!(
402 + "Unexpected 'if' where consequent is already scheduled (bb{})",
403 + consequent.0
404 + ),
405 None,
406 ));
407 } else {
402 - Some(self.traverse_block(alt)?)
403 - }
404 - } else {
405 - None
406 - };
407 -
408 - self.cx.unschedule_all(&schedule_ids)?;
409 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
410 - terminal: ReactiveTerminal::If {
411 - test: test.clone(),
412 - consequent: consequent_block,
413 - alternate: alternate_block,
414 - id: *id,
415 - loc: *loc,
416 - },
417 - label: fallthrough_id.map(|ft| ReactiveLabel {
418 - id: ft,
419 - implicit: false,
420 - }),
421 - }));
422 -
423 - next_block = fallthrough_id;
424 - }
425 -
426 - Terminal::Switch {
427 - test,
428 - cases,
429 - fallthrough,
430 - id,
431 - loc,
432 - } => {
433 - // TS: reachable(fallthrough) && !isScheduled(fallthrough)
434 - let fallthrough_id = if self.cx.reachable(*fallthrough)
435 - && !self.cx.is_scheduled(*fallthrough)
436 - {
437 - Some(*fallthrough)
438 - } else {
439 - None
440 - };
441 - if let Some(ft) = fallthrough_id {
442 - schedule_ids.push(self.cx.schedule(ft, "switch")?);
443 - }
444 -
445 - // TS processes cases in reverse order, then reverses the result.
446 - // This ensures that later cases are scheduled when earlier cases
447 - // are traversed, matching fallthrough semantics.
448 - let mut reactive_cases = Vec::new();
449 - for case in cases.iter().rev() {
450 - let case_block_id = case.block;
408 + self.traverse_block(*consequent)?
409 + };
410
452 - if self.cx.is_scheduled(case_block_id) {
453 - // TS: asserts case.block === fallthrough, then skips (return)
454 - if case_block_id != *fallthrough {
411 + let alternate_block = if let Some(alt) = alternate_id {
412 + if self.cx.is_scheduled(alt) {
413 return Err(CompilerDiagnostic::new(
414 ErrorCategory::Invariant,
457 - "Unexpected 'switch' where a case is already scheduled and block is not the fallthrough".to_string(),
415 + format!(
416 + "Unexpected 'if' where the alternate is already scheduled (bb{})",
417 + alt.0
418 + ),
419 None,
420 ));
421 + } else {
422 + Some(self.traverse_block(alt)?)
423 }
461 - continue;
462 - }
424 + } else {
425 + None
426 + };
427
464 - let consequent = self.traverse_block(case_block_id)?;
465 - let case_schedule_id = self.cx.schedule(case_block_id, "case")?;
466 - schedule_ids.push(case_schedule_id);
428 + self.cx.unschedule_all(&schedule_ids)?;
429 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
430 + terminal: ReactiveTerminal::If {
431 + test: test.clone(),
432 + consequent: consequent_block,
433 + alternate: alternate_block,
434 + id: *id,
435 + loc: *loc,
436 + },
437 + label: fallthrough_id.map(|ft| ReactiveLabel {
438 + id: ft,
439 + implicit: false,
440 + }),
441 + }));
442
468 - reactive_cases.push(ReactiveSwitchCase {
469 - test: case.test.clone(),
470 - block: Some(consequent),
471 - });
443 + next_block = fallthrough_id;
444 }
473 - reactive_cases.reverse();
474 -
475 - self.cx.unschedule_all(&schedule_ids)?;
476 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
477 - terminal: ReactiveTerminal::Switch {
478 - test: test.clone(),
479 - cases: reactive_cases,
480 - id: *id,
481 - loc: *loc,
482 - },
483 - label: fallthrough_id.map(|ft| ReactiveLabel {
484 - id: ft,
485 - implicit: false,
486 - }),
487 - }));
488 -
489 - next_block = fallthrough_id;
490 - }
491 -
492 - Terminal::DoWhile {
493 - loop_block,
494 - test,
495 - fallthrough,
496 - id,
497 - loc,
498 - } => {
499 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
500 - Some(*fallthrough)
501 - } else {
502 - None
503 - };
504 - let loop_id = if !self.cx.is_scheduled(*loop_block)
505 - && *loop_block != *fallthrough
506 - {
507 - Some(*loop_block)
508 - } else {
509 - None
510 - };
511 -
512 - schedule_ids.push(self.cx.schedule_loop(
513 - *fallthrough,
514 - *test,
515 - Some(*loop_block),
516 - )?);
517 -
518 - let loop_body = if let Some(lid) = loop_id {
519 - self.traverse_block(lid)?
520 - } else {
521 - return Err(CompilerDiagnostic::new(
522 - ErrorCategory::Invariant,
523 - "Unexpected 'do-while' where the loop is already scheduled",
524 - None,
525 - ));
526 - };
527 - let test_result = self.visit_value_block(*test, *loc, None)?;
445
529 - self.cx.unschedule_all(&schedule_ids)?;
530 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
531 - terminal: ReactiveTerminal::DoWhile {
532 - loop_block: loop_body,
533 - test: test_result.value,
534 - id: *id,
535 - loc: *loc,
536 - },
537 - label: fallthrough_id.map(|ft| ReactiveLabel {
538 - id: ft,
539 - implicit: false,
540 - }),
541 - }));
542 -
543 - next_block = fallthrough_id;
544 - }
545 -
546 - Terminal::While {
547 - test,
548 - loop_block,
549 - fallthrough,
550 - id,
551 - loc,
552 - } => {
553 - // TS: reachable(fallthrough) && !isScheduled(fallthrough)
554 - let fallthrough_id = if self.cx.reachable(*fallthrough)
555 - && !self.cx.is_scheduled(*fallthrough)
556 - {
557 - Some(*fallthrough)
558 - } else {
559 - None
560 - };
561 - let loop_id = if !self.cx.is_scheduled(*loop_block)
562 - && *loop_block != *fallthrough
563 - {
564 - Some(*loop_block)
565 - } else {
566 - None
567 - };
568 -
569 - schedule_ids.push(self.cx.schedule_loop(
570 - *fallthrough,
571 - *test,
572 - Some(*loop_block),
573 - )?);
446 + Terminal::Switch {
447 + test,
448 + cases,
449 + fallthrough,
450 + id,
451 + loc,
452 + } => {
453 + // TS: reachable(fallthrough) && !isScheduled(fallthrough)
454 + let fallthrough_id =
455 + if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
456 + Some(*fallthrough)
457 + } else {
458 + None
459 + };
460 + if let Some(ft) = fallthrough_id {
461 + schedule_ids.push(self.cx.schedule(ft, "switch")?);
462 + }
463
575 - let test_result = self.visit_value_block(*test, *loc, None)?;
464 + // TS processes cases in reverse order, then reverses the result.
465 + // This ensures that later cases are scheduled when earlier cases
466 + // are traversed, matching fallthrough semantics.
467 + let mut reactive_cases = Vec::new();
468 + for case in cases.iter().rev() {
469 + let case_block_id = case.block;
470 +
471 + if self.cx.is_scheduled(case_block_id) {
472 + // TS: asserts case.block === fallthrough, then skips (return)
473 + if case_block_id != *fallthrough {
474 + return Err(CompilerDiagnostic::new(
475 + ErrorCategory::Invariant,
476 + "Unexpected 'switch' where a case is already scheduled and block is not the fallthrough".to_string(),
477 + None,
478 + ));
479 + }
480 + continue;
481 + }
482
577 - let loop_body = if let Some(lid) = loop_id {
578 - self.traverse_block(lid)?
579 - } else {
580 - return Err(CompilerDiagnostic::new(
581 - ErrorCategory::Invariant,
582 - "Unexpected 'while' where the loop is already scheduled",
583 - None,
584 - ));
585 - };
483 + let consequent = self.traverse_block(case_block_id)?;
484 + let case_schedule_id = self.cx.schedule(case_block_id, "case")?;
485 + schedule_ids.push(case_schedule_id);
486
587 - self.cx.unschedule_all(&schedule_ids)?;
588 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
589 - terminal: ReactiveTerminal::While {
590 - test: test_result.value,
591 - loop_block: loop_body,
592 - id: *id,
593 - loc: *loc,
594 - },
595 - label: fallthrough_id.map(|ft| ReactiveLabel {
596 - id: ft,
597 - implicit: false,
598 - }),
599 - }));
487 + reactive_cases.push(ReactiveSwitchCase {
488 + test: case.test.clone(),
489 + block: Some(consequent),
490 + });
491 + }
492 + reactive_cases.reverse();
493 +
494 + self.cx.unschedule_all(&schedule_ids)?;
495 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
496 + terminal: ReactiveTerminal::Switch {
497 + test: test.clone(),
498 + cases: reactive_cases,
499 + id: *id,
500 + loc: *loc,
501 + },
502 + label: fallthrough_id.map(|ft| ReactiveLabel {
503 + id: ft,
504 + implicit: false,
505 + }),
506 + }));
507
601 - next_block = fallthrough_id;
602 - }
508 + next_block = fallthrough_id;
509 + }
510
604 - Terminal::For {
605 - init,
606 - test,
607 - update,
608 - loop_block,
609 - fallthrough,
610 - id,
611 - loc,
612 - } => {
613 - let loop_id = if !self.cx.is_scheduled(*loop_block)
614 - && *loop_block != *fallthrough
615 - {
616 - Some(*loop_block)
617 - } else {
618 - None
619 - };
511 + Terminal::DoWhile {
512 + loop_block,
513 + test,
514 + fallthrough,
515 + id,
516 + loc,
517 + } => {
518 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
519 + Some(*fallthrough)
520 + } else {
521 + None
522 + };
523 + let loop_id =
524 + if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
525 + Some(*loop_block)
526 + } else {
527 + None
528 + };
529 +
530 + schedule_ids.push(self.cx.schedule_loop(
531 + *fallthrough,
532 + *test,
533 + Some(*loop_block),
534 + )?);
535 +
536 + let loop_body = if let Some(lid) = loop_id {
537 + self.traverse_block(lid)?
538 + } else {
539 + return Err(CompilerDiagnostic::new(
540 + ErrorCategory::Invariant,
541 + "Unexpected 'do-while' where the loop is already scheduled",
542 + None,
543 + ));
544 + };
545 + let test_result = self.visit_value_block(*test, *loc, None)?;
546 +
547 + self.cx.unschedule_all(&schedule_ids)?;
548 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
549 + terminal: ReactiveTerminal::DoWhile {
550 + loop_block: loop_body,
551 + test: test_result.value,
552 + id: *id,
553 + loc: *loc,
554 + },
555 + label: fallthrough_id.map(|ft| ReactiveLabel {
556 + id: ft,
557 + implicit: false,
558 + }),
559 + }));
560
621 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
622 - Some(*fallthrough)
623 - } else {
624 - None
625 - };
561 + next_block = fallthrough_id;
562 + }
563
627 - // Continue block is update (if present) or test
628 - let continue_block = update.unwrap_or(*test);
629 - schedule_ids.push(self.cx.schedule_loop(
630 - *fallthrough,
631 - continue_block,
632 - Some(*loop_block),
633 - )?);
564 + Terminal::While {
565 + test,
566 + loop_block,
567 + fallthrough,
568 + id,
569 + loc,
570 + } => {
571 + // TS: reachable(fallthrough) && !isScheduled(fallthrough)
572 + let fallthrough_id =
573 + if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
574 + Some(*fallthrough)
575 + } else {
576 + None
577 + };
578 + let loop_id =
579 + if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
580 + Some(*loop_block)
581 + } else {
582 + None
583 + };
584 +
585 + schedule_ids.push(self.cx.schedule_loop(
586 + *fallthrough,
587 + *test,
588 + Some(*loop_block),
589 + )?);
590 +
591 + let test_result = self.visit_value_block(*test, *loc, None)?;
592 +
593 + let loop_body = if let Some(lid) = loop_id {
594 + self.traverse_block(lid)?
595 + } else {
596 + return Err(CompilerDiagnostic::new(
597 + ErrorCategory::Invariant,
598 + "Unexpected 'while' where the loop is already scheduled",
599 + None,
600 + ));
601 + };
602
635 - let init_result = self.visit_value_block(*init, *loc, None)?;
636 - let init_value = self.value_block_result_to_sequence(init_result, *loc);
603 + self.cx.unschedule_all(&schedule_ids)?;
604 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
605 + terminal: ReactiveTerminal::While {
606 + test: test_result.value,
607 + loop_block: loop_body,
608 + id: *id,
609 + loc: *loc,
610 + },
611 + label: fallthrough_id.map(|ft| ReactiveLabel {
612 + id: ft,
613 + implicit: false,
614 + }),
615 + }));
616
638 - let test_result = self.visit_value_block(*test, *loc, None)?;
617 + next_block = fallthrough_id;
618 + }
619
640 - let update_result = match update {
641 - Some(u) => Some(self.visit_value_block(*u, *loc, None)?),
642 - None => None,
643 - };
620 + Terminal::For {
621 + init,
622 + test,
623 + update,
624 + loop_block,
625 + fallthrough,
626 + id,
627 + loc,
628 + } => {
629 + let loop_id =
630 + if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
631 + Some(*loop_block)
632 + } else {
633 + None
634 + };
635 +
636 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
637 + Some(*fallthrough)
638 + } else {
639 + None
640 + };
641
645 - let loop_body = if let Some(lid) = loop_id {
646 - self.traverse_block(lid)?
647 - } else {
648 - return Err(CompilerDiagnostic::new(
649 - ErrorCategory::Invariant,
650 - "Unexpected 'for' where the loop is already scheduled",
651 - None,
652 - ));
653 - };
642 + // Continue block is update (if present) or test
643 + let continue_block = update.unwrap_or(*test);
644 + schedule_ids.push(self.cx.schedule_loop(
645 + *fallthrough,
646 + continue_block,
647 + Some(*loop_block),
648 + )?);
649
655 - self.cx.unschedule_all(&schedule_ids)?;
656 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
657 - terminal: ReactiveTerminal::For {
658 - init: init_value,
659 - test: test_result.value,
660 - update: update_result.map(|r| r.value),
661 - loop_block: loop_body,
662 - id: *id,
663 - loc: *loc,
664 - },
665 - label: fallthrough_id.map(|ft| ReactiveLabel {
666 - id: ft,
667 - implicit: false,
668 - }),
669 - }));
650 + let init_result = self.visit_value_block(*init, *loc, None)?;
651 + let init_value = self.value_block_result_to_sequence(init_result, *loc);
652
671 - next_block = fallthrough_id;
672 - }
653 + let test_result = self.visit_value_block(*test, *loc, None)?;
654
674 - Terminal::ForOf {
675 - init,
676 - test,
677 - loop_block,
678 - fallthrough,
679 - id,
680 - loc,
681 - } => {
682 - let loop_id = if !self.cx.is_scheduled(*loop_block)
683 - && *loop_block != *fallthrough
684 - {
685 - Some(*loop_block)
686 - } else {
687 - None
688 - };
655 + let update_result = match update {
656 + Some(u) => Some(self.visit_value_block(*u, *loc, None)?),
657 + None => None,
658 + };
659
690 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
691 - Some(*fallthrough)
692 - } else {
693 - None
694 - };
660 + let loop_body = if let Some(lid) = loop_id {
661 + self.traverse_block(lid)?
662 + } else {
663 + return Err(CompilerDiagnostic::new(
664 + ErrorCategory::Invariant,
665 + "Unexpected 'for' where the loop is already scheduled",
666 + None,
667 + ));
668 + };
669
696 - // TS: scheduleLoop(fallthrough, init, loop)
697 - schedule_ids.push(self.cx.schedule_loop(
698 - *fallthrough,
699 - *init,
700 - Some(*loop_block),
701 - )?);
670 + self.cx.unschedule_all(&schedule_ids)?;
671 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
672 + terminal: ReactiveTerminal::For {
673 + init: init_value,
674 + test: test_result.value,
675 + update: update_result.map(|r| r.value),
676 + loop_block: loop_body,
677 + id: *id,
678 + loc: *loc,
679 + },
680 + label: fallthrough_id.map(|ft| ReactiveLabel {
681 + id: ft,
682 + implicit: false,
683 + }),
684 + }));
685
703 - let init_result = self.visit_value_block(*init, *loc, None)?;
704 - let init_value = self.value_block_result_to_sequence(init_result, *loc);
686 + next_block = fallthrough_id;
687 + }
688
706 - let test_result = self.visit_value_block(*test, *loc, None)?;
707 - let test_value = self.value_block_result_to_sequence(test_result, *loc);
689 + Terminal::ForOf {
690 + init,
691 + test,
692 + loop_block,
693 + fallthrough,
694 + id,
695 + loc,
696 + } => {
697 + let loop_id =
698 + if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
699 + Some(*loop_block)
700 + } else {
701 + None
702 + };
703 +
704 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
705 + Some(*fallthrough)
706 + } else {
707 + None
708 + };
709
709 - let loop_body = if let Some(lid) = loop_id {
710 - self.traverse_block(lid)?
711 - } else {
712 - return Err(CompilerDiagnostic::new(
713 - ErrorCategory::Invariant,
714 - "Unexpected 'for-of' where the loop is already scheduled",
715 - None,
716 - ));
717 - };
710 + // TS: scheduleLoop(fallthrough, init, loop)
711 + schedule_ids.push(self.cx.schedule_loop(
712 + *fallthrough,
713 + *init,
714 + Some(*loop_block),
715 + )?);
716
719 - self.cx.unschedule_all(&schedule_ids)?;
720 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
721 - terminal: ReactiveTerminal::ForOf {
722 - init: init_value,
723 - test: test_value,
724 - loop_block: loop_body,
725 - id: *id,
726 - loc: *loc,
727 - },
728 - label: fallthrough_id.map(|ft| ReactiveLabel {
729 - id: ft,
730 - implicit: false,
731 - }),
732 - }));
717 + let init_result = self.visit_value_block(*init, *loc, None)?;
718 + let init_value = self.value_block_result_to_sequence(init_result, *loc);
719
734 - next_block = fallthrough_id;
735 - }
720 + let test_result = self.visit_value_block(*test, *loc, None)?;
721 + let test_value = self.value_block_result_to_sequence(test_result, *loc);
722
737 - Terminal::ForIn {
738 - init,
739 - loop_block,
740 - fallthrough,
741 - id,
742 - loc,
743 - } => {
744 - let loop_id = if !self.cx.is_scheduled(*loop_block)
745 - && *loop_block != *fallthrough
746 - {
747 - Some(*loop_block)
748 - } else {
749 - None
750 - };
723 + let loop_body = if let Some(lid) = loop_id {
724 + self.traverse_block(lid)?
725 + } else {
726 + return Err(CompilerDiagnostic::new(
727 + ErrorCategory::Invariant,
728 + "Unexpected 'for-of' where the loop is already scheduled",
729 + None,
730 + ));
731 + };
732
752 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
753 - Some(*fallthrough)
754 - } else {
755 - None
756 - };
733 + self.cx.unschedule_all(&schedule_ids)?;
734 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
735 + terminal: ReactiveTerminal::ForOf {
736 + init: init_value,
737 + test: test_value,
738 + loop_block: loop_body,
739 + id: *id,
740 + loc: *loc,
741 + },
742 + label: fallthrough_id.map(|ft| ReactiveLabel {
743 + id: ft,
744 + implicit: false,
745 + }),
746 + }));
747
758 - schedule_ids.push(self.cx.schedule_loop(
759 - *fallthrough,
760 - *init,
761 - Some(*loop_block),
762 - )?);
748 + next_block = fallthrough_id;
749 + }
750
764 - let init_result = self.visit_value_block(*init, *loc, None)?;
765 - let init_value = self.value_block_result_to_sequence(init_result, *loc);
751 + Terminal::ForIn {
752 + init,
753 + loop_block,
754 + fallthrough,
755 + id,
756 + loc,
757 + } => {
758 + let loop_id =
759 + if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
760 + Some(*loop_block)
761 + } else {
762 + None
763 + };
764 +
765 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
766 + Some(*fallthrough)
767 + } else {
768 + None
769 + };
770
767 - let loop_body = if let Some(lid) = loop_id {
768 - self.traverse_block(lid)?
769 - } else {
770 - return Err(CompilerDiagnostic::new(
771 - ErrorCategory::Invariant,
772 - "Unexpected 'for-in' where the loop is already scheduled",
773 - None,
774 - ));
775 - };
771 + schedule_ids.push(self.cx.schedule_loop(
772 + *fallthrough,
773 + *init,
774 + Some(*loop_block),
775 + )?);
776
777 - self.cx.unschedule_all(&schedule_ids)?;
778 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
779 - terminal: ReactiveTerminal::ForIn {
780 - init: init_value,
781 - loop_block: loop_body,
782 - id: *id,
783 - loc: *loc,
784 - },
785 - label: fallthrough_id.map(|ft| ReactiveLabel {
786 - id: ft,
787 - implicit: false,
788 - }),
789 - }));
777 + let init_result = self.visit_value_block(*init, *loc, None)?;
778 + let init_value = self.value_block_result_to_sequence(init_result, *loc);
779
791 - next_block = fallthrough_id;
792 - }
780 + let loop_body = if let Some(lid) = loop_id {
781 + self.traverse_block(lid)?
782 + } else {
783 + return Err(CompilerDiagnostic::new(
784 + ErrorCategory::Invariant,
785 + "Unexpected 'for-in' where the loop is already scheduled",
786 + None,
787 + ));
788 + };
789
794 - Terminal::Label {
795 - block: label_block,
796 - fallthrough,
797 - id,
798 - loc,
799 - } => {
800 - // TS: reachable(fallthrough) && !isScheduled(fallthrough)
801 - let fallthrough_id = if self.cx.reachable(*fallthrough)
802 - && !self.cx.is_scheduled(*fallthrough)
803 - {
804 - Some(*fallthrough)
805 - } else {
806 - None
807 - };
808 - if let Some(ft) = fallthrough_id {
809 - schedule_ids.push(self.cx.schedule(ft, "if")?);
810 - }
790 + self.cx.unschedule_all(&schedule_ids)?;
791 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
792 + terminal: ReactiveTerminal::ForIn {
793 + init: init_value,
794 + loop_block: loop_body,
795 + id: *id,
796 + loc: *loc,
797 + },
798 + label: fallthrough_id.map(|ft| ReactiveLabel {
799 + id: ft,
800 + implicit: false,
801 + }),
802 + }));
803
812 - if self.cx.is_scheduled(*label_block) {
813 - return Err(CompilerDiagnostic::new(
814 - ErrorCategory::Invariant,
815 - "Unexpected 'label' where the block is already scheduled".to_string(),
816 - None,
817 - ));
804 + next_block = fallthrough_id;
805 }
819 - let label_body = self.traverse_block(*label_block)?;
806
821 - self.cx.unschedule_all(&schedule_ids)?;
822 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
823 - terminal: ReactiveTerminal::Label {
824 - block: label_body,
825 - id: *id,
826 - loc: *loc,
827 - },
828 - label: fallthrough_id.map(|ft| ReactiveLabel {
829 - id: ft,
830 - implicit: false,
831 - }),
832 - }));
807 + Terminal::Label {
808 + block: label_block,
809 + fallthrough,
810 + id,
811 + loc,
812 + } => {
813 + // TS: reachable(fallthrough) && !isScheduled(fallthrough)
814 + let fallthrough_id =
815 + if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
816 + Some(*fallthrough)
817 + } else {
818 + None
819 + };
820 + if let Some(ft) = fallthrough_id {
821 + schedule_ids.push(self.cx.schedule(ft, "if")?);
822 + }
823
834 - next_block = fallthrough_id;
835 - }
824 + if self.cx.is_scheduled(*label_block) {
825 + return Err(CompilerDiagnostic::new(
826 + ErrorCategory::Invariant,
827 + "Unexpected 'label' where the block is already scheduled".to_string(),
828 + None,
829 + ));
830 + }
831 + let label_body = self.traverse_block(*label_block)?;
832 +
833 + self.cx.unschedule_all(&schedule_ids)?;
834 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
835 + terminal: ReactiveTerminal::Label {
836 + block: label_body,
837 + id: *id,
838 + loc: *loc,
839 + },
840 + label: fallthrough_id.map(|ft| ReactiveLabel {
841 + id: ft,
842 + implicit: false,
843 + }),
844 + }));
845
837 - Terminal::Sequence { .. }
838 - | Terminal::Optional { .. }
839 - | Terminal::Ternary { .. }
840 - | Terminal::Logical { .. } => {
841 - let fallthrough = match &terminal {
842 - Terminal::Sequence { fallthrough, .. }
843 - | Terminal::Optional { fallthrough, .. }
844 - | Terminal::Ternary { fallthrough, .. }
845 - | Terminal::Logical { fallthrough, .. } => *fallthrough,
846 - _ => unreachable!(),
847 - };
848 - let fallthrough_id = if !self.cx.is_scheduled(fallthrough) {
849 - Some(fallthrough)
850 - } else {
851 - None
852 - };
853 - if let Some(ft) = fallthrough_id {
854 - schedule_ids.push(self.cx.schedule(ft, "if")?);
846 + next_block = fallthrough_id;
847 }
848
857 - let result = self.visit_value_block_terminal(&terminal)?;
858 - self.cx.unschedule_all(&schedule_ids)?;
859 - block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {
860 - id: result.id,
861 - lvalue: Some(result.place),
862 - value: result.value,
863 - effects: None,
864 - loc: *terminal_loc(&terminal),
865 - }));
849 + Terminal::Sequence { .. }
850 + | Terminal::Optional { .. }
851 + | Terminal::Ternary { .. }
852 + | Terminal::Logical { .. } => {
853 + let fallthrough = match &terminal {
854 + Terminal::Sequence { fallthrough, .. }
855 + | Terminal::Optional { fallthrough, .. }
856 + | Terminal::Ternary { fallthrough, .. }
857 + | Terminal::Logical { fallthrough, .. } => *fallthrough,
858 + _ => unreachable!(),
859 + };
860 + let fallthrough_id = if !self.cx.is_scheduled(fallthrough) {
861 + Some(fallthrough)
862 + } else {
863 + None
864 + };
865 + if let Some(ft) = fallthrough_id {
866 + schedule_ids.push(self.cx.schedule(ft, "if")?);
867 + }
868
867 - next_block = fallthrough_id;
868 - }
869 + let result = self.visit_value_block_terminal(&terminal)?;
870 + self.cx.unschedule_all(&schedule_ids)?;
871 + block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {
872 + id: result.id,
873 + lvalue: Some(result.place),
874 + value: result.value,
875 + effects: None,
876 + loc: *terminal_loc(&terminal),
877 + }));
878
870 - Terminal::Goto {
871 - block: goto_block,
872 - variant,
873 - id,
874 - loc,
875 - } => {
876 - match variant {
877 - GotoVariant::Break => {
878 - if let Some(stmt) = self.visit_break(*goto_block, *id, *loc)? {
879 + next_block = fallthrough_id;
880 + }
881 +
882 + Terminal::Goto {
883 + block: goto_block,
884 + variant,
885 + id,
886 + loc,
887 + } => {
888 + match variant {
889 + GotoVariant::Break => {
890 + if let Some(stmt) = self.visit_break(*goto_block, *id, *loc)? {
891 + block_value.push(stmt);
892 + }
893 + }
894 + GotoVariant::Continue => {
895 + let stmt = self.visit_continue(*goto_block, *id, *loc)?;
896 block_value.push(stmt);
897 }
881 - }
882 - GotoVariant::Continue => {
883 - let stmt = self.visit_continue(*goto_block, *id, *loc)?;
884 - block_value.push(stmt);
885 - }
886 - GotoVariant::Try => {
887 - // noop
898 + GotoVariant::Try => {
899 + // noop
900 + }
901 }
902 }
890 - }
903
892 - Terminal::MaybeThrow {
893 - continuation, ..
894 - } => {
895 - if !self.cx.is_scheduled(*continuation) {
896 - next_block = Some(*continuation);
904 + Terminal::MaybeThrow { continuation, .. } => {
905 + if !self.cx.is_scheduled(*continuation) {
906 + next_block = Some(*continuation);
907 + }
908 }
898 - }
909
900 - Terminal::Try {
901 - block: try_block,
902 - handler_binding,
903 - handler,
904 - fallthrough,
905 - id,
906 - loc,
907 - } => {
908 - let fallthrough_id = if self.cx.reachable(*fallthrough)
909 - && !self.cx.is_scheduled(*fallthrough)
910 - {
911 - Some(*fallthrough)
912 - } else {
913 - None
914 - };
915 - if let Some(ft) = fallthrough_id {
916 - schedule_ids.push(self.cx.schedule(ft, "if")?);
910 + Terminal::Try {
911 + block: try_block,
912 + handler_binding,
913 + handler,
914 + fallthrough,
915 + id,
916 + loc,
917 + } => {
918 + let fallthrough_id =
919 + if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
920 + Some(*fallthrough)
921 + } else {
922 + None
923 + };
924 + if let Some(ft) = fallthrough_id {
925 + schedule_ids.push(self.cx.schedule(ft, "if")?);
926 + }
927 + self.cx.schedule_catch_handler(*handler);
928 +
929 + let try_body = self.traverse_block(*try_block)?;
930 + let handler_body = self.traverse_block(*handler)?;
931 +
932 + self.cx.unschedule_all(&schedule_ids)?;
933 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
934 + terminal: ReactiveTerminal::Try {
935 + block: try_body,
936 + handler_binding: handler_binding.clone(),
937 + handler: handler_body,
938 + id: *id,
939 + loc: *loc,
940 + },
941 + label: fallthrough_id.map(|ft| ReactiveLabel {
942 + id: ft,
943 + implicit: false,
944 + }),
945 + }));
946 +
947 + next_block = fallthrough_id;
948 }
918 - self.cx.schedule_catch_handler(*handler);
949
920 - let try_body = self.traverse_block(*try_block)?;
921 - let handler_body = self.traverse_block(*handler)?;
950 + Terminal::Scope {
951 + fallthrough,
952 + block: scope_block,
953 + scope,
954 + ..
955 + } => {
956 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
957 + Some(*fallthrough)
958 + } else {
959 + None
960 + };
961 + if let Some(ft) = fallthrough_id {
962 + schedule_ids.push(self.cx.schedule(ft, "if")?);
963 + self.cx.scope_fallthroughs.insert(ft);
964 + }
965
923 - self.cx.unschedule_all(&schedule_ids)?;
924 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
925 - terminal: ReactiveTerminal::Try {
926 - block: try_body,
927 - handler_binding: handler_binding.clone(),
928 - handler: handler_body,
929 - id: *id,
930 - loc: *loc,
931 - },
932 - label: fallthrough_id.map(|ft| ReactiveLabel {
933 - id: ft,
934 - implicit: false,
935 - }),
936 - }));
966 + if self.cx.is_scheduled(*scope_block) {
967 + return Err(CompilerDiagnostic::new(
968 + ErrorCategory::Invariant,
969 + "Unexpected 'scope' where the block is already scheduled".to_string(),
970 + None,
971 + ));
972 + }
973 + let scope_body = self.traverse_block(*scope_block)?;
974
938 - next_block = fallthrough_id;
939 - }
975 + self.cx.unschedule_all(&schedule_ids)?;
976 + block_value.push(ReactiveStatement::Scope(ReactiveScopeBlock {
977 + scope: *scope,
978 + instructions: scope_body,
979 + }));
980
941 - Terminal::Scope {
942 - fallthrough,
943 - block: scope_block,
944 - scope,
945 - ..
946 - } => {
947 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
948 - Some(*fallthrough)
949 - } else {
950 - None
951 - };
952 - if let Some(ft) = fallthrough_id {
953 - schedule_ids.push(self.cx.schedule(ft, "if")?);
954 - self.cx.scope_fallthroughs.insert(ft);
981 + next_block = fallthrough_id;
982 }
983
957 - if self.cx.is_scheduled(*scope_block) {
958 - return Err(CompilerDiagnostic::new(
959 - ErrorCategory::Invariant,
960 - "Unexpected 'scope' where the block is already scheduled".to_string(),
961 - None,
962 - ));
963 - }
964 - let scope_body = self.traverse_block(*scope_block)?;
984 + Terminal::PrunedScope {
985 + fallthrough,
986 + block: scope_block,
987 + scope,
988 + ..
989 + } => {
990 + let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
991 + Some(*fallthrough)
992 + } else {
993 + None
994 + };
995 + if let Some(ft) = fallthrough_id {
996 + schedule_ids.push(self.cx.schedule(ft, "if")?);
997 + self.cx.scope_fallthroughs.insert(ft);
998 + }
999
966 - self.cx.unschedule_all(&schedule_ids)?;
967 - block_value.push(ReactiveStatement::Scope(ReactiveScopeBlock {
968 - scope: *scope,
969 - instructions: scope_body,
970 - }));
1000 + if self.cx.is_scheduled(*scope_block) {
1001 + return Err(CompilerDiagnostic::new(
1002 + ErrorCategory::Invariant,
1003 + "Unexpected 'scope' where the block is already scheduled".to_string(),
1004 + None,
1005 + ));
1006 + }
1007 + let scope_body = self.traverse_block(*scope_block)?;
1008
972 - next_block = fallthrough_id;
973 - }
1009 + self.cx.unschedule_all(&schedule_ids)?;
1010 + block_value.push(ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock {
1011 + scope: *scope,
1012 + instructions: scope_body,
1013 + }));
1014
975 - Terminal::PrunedScope {
976 - fallthrough,
977 - block: scope_block,
978 - scope,
979 - ..
980 - } => {
981 - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {
982 - Some(*fallthrough)
983 - } else {
984 - None
985 - };
986 - if let Some(ft) = fallthrough_id {
987 - schedule_ids.push(self.cx.schedule(ft, "if")?);
988 - self.cx.scope_fallthroughs.insert(ft);
1015 + next_block = fallthrough_id;
1016 }
1017
991 - if self.cx.is_scheduled(*scope_block) {
992 - return Err(CompilerDiagnostic::new(
993 - ErrorCategory::Invariant,
994 - "Unexpected 'scope' where the block is already scheduled".to_string(),
995 - None,
996 - ));
1018 + Terminal::Return { value, id, loc, .. } => {
1019 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1020 + terminal: ReactiveTerminal::Return {
1021 + value: value.clone(),
1022 + id: *id,
1023 + loc: *loc,
1024 + },
1025 + label: None,
1026 + }));
1027 }
998 - let scope_body = self.traverse_block(*scope_block)?;
999 -
1000 - self.cx.unschedule_all(&schedule_ids)?;
1001 - block_value.push(ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock {
1002 - scope: *scope,
1003 - instructions: scope_body,
1004 - }));
1005 -
1006 - next_block = fallthrough_id;
1007 - }
1008 -
1009 - Terminal::Return { value, id, loc, .. } => {
1010 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1011 - terminal: ReactiveTerminal::Return {
1012 - value: value.clone(),
1013 - id: *id,
1014 - loc: *loc,
1015 - },
1016 - label: None,
1017 - }));
1018 - }
1019 -
1020 - Terminal::Throw { value, id, loc } => {
1021 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1022 - terminal: ReactiveTerminal::Throw {
1023 - value: value.clone(),
1024 - id: *id,
1025 - loc: *loc,
1026 - },
1027 - label: None,
1028 - }));
1029 - }
1030 -
1031 - Terminal::Unreachable { .. } => {
1032 - // noop
1033 - }
1028
1035 - Terminal::Unsupported { .. } => {
1036 - return Err(CompilerDiagnostic::new(
1037 - ErrorCategory::Invariant,
1038 - "Unexpected unsupported terminal",
1039 - None,
1040 - ));
1041 - }
1029 + Terminal::Throw { value, id, loc } => {
1030 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1031 + terminal: ReactiveTerminal::Throw {
1032 + value: value.clone(),
1033 + id: *id,
1034 + loc: *loc,
1035 + },
1036 + label: None,
1037 + }));
1038 + }
1039
1043 - Terminal::Branch {
1044 - test,
1045 - consequent,
1046 - alternate,
1047 - id,
1048 - loc,
1049 - ..
1050 - } => {
1051 - let consequent_block = if self.cx.is_scheduled(*consequent) {
1052 - if let Some(stmt) = self.visit_break(*consequent, *id, *loc)? {
1053 - vec![stmt]
1054 - } else {
1055 - Vec::new()
1056 - }
1057 - } else {
1058 - self.traverse_block(*consequent)?
1059 - };
1040 + Terminal::Unreachable { .. } => {
1041 + // noop
1042 + }
1043
1061 - if self.cx.is_scheduled(*alternate) {
1044 + Terminal::Unsupported { .. } => {
1045 return Err(CompilerDiagnostic::new(
1046 ErrorCategory::Invariant,
1064 - "Unexpected 'branch' where the alternate is already scheduled".to_string(),
1047 + "Unexpected unsupported terminal",
1048 None,
1049 ));
1050 }
1068 - let alternate_block = self.traverse_block(*alternate)?;
1051
1070 - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1071 - terminal: ReactiveTerminal::If {
1072 - test: test.clone(),
1073 - consequent: consequent_block,
1074 - alternate: Some(alternate_block),
1075 - id: *id,
1076 - loc: *loc,
1077 - },
1078 - label: None,
1079 - }));
1052 + Terminal::Branch {
1053 + test,
1054 + consequent,
1055 + alternate,
1056 + id,
1057 + loc,
1058 + ..
1059 + } => {
1060 + let consequent_block = if self.cx.is_scheduled(*consequent) {
1061 + if let Some(stmt) = self.visit_break(*consequent, *id, *loc)? {
1062 + vec![stmt]
1063 + } else {
1064 + Vec::new()
1065 + }
1066 + } else {
1067 + self.traverse_block(*consequent)?
1068 + };
1069 +
1070 + if self.cx.is_scheduled(*alternate) {
1071 + return Err(CompilerDiagnostic::new(
1072 + ErrorCategory::Invariant,
1073 + "Unexpected 'branch' where the alternate is already scheduled"
1074 + .to_string(),
1075 + None,
1076 + ));
1077 + }
1078 + let alternate_block = self.traverse_block(*alternate)?;
1079 +
1080 + block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1081 + terminal: ReactiveTerminal::If {
1082 + test: test.clone(),
1083 + consequent: consequent_block,
1084 + alternate: Some(alternate_block),
1085 + id: *id,
1086 + loc: *loc,
1087 + },
1088 + label: None,
1089 + }));
1090 + }
1091 + }
1092 + match next_block {
1093 + Some(nb) => block_id = nb,
1094 + None => return Ok(()),
1095 }
1081 - }
1082 - match next_block {
1083 - Some(nb) => block_id = nb,
1084 - None => return Ok(()),
1085 - }
1096 } // end loop
1097 }
1098
@@ -1117,9 +1127,7 @@ impl<'a, 'b> Driver<'a, 'b> {
1127
1128 match &terminal {
1129 Terminal::Branch {
1120 - test,
1121 - id: term_id,
1122 - ..
1130 + test, id: term_id, ..
1131 } => {
1132 if instructions.is_empty() {
1133 Ok(ValueBlockResult {
@@ -1141,7 +1149,8 @@ impl<'a, 'b> Driver<'a, 'b> {
1149 ErrorCategory::Invariant,
1150 "Unexpected empty block with `goto` terminal",
1151 Some(format!("Block bb{} is empty", block_id.0)),
1144 - ).with_detail(CompilerDiagnosticDetail::Error {
1152 + )
1153 + .with_detail(CompilerDiagnosticDetail::Error {
1154 loc,
1155 message: Some("Unexpected empty block with `goto` terminal".to_string()),
1156 identifier_name: None,
@@ -1149,9 +1158,7 @@ impl<'a, 'b> Driver<'a, 'b> {
1158 }
1159 Ok(self.extract_value_block_result(&instructions, block_id_val, loc))
1160 }
1152 - Terminal::MaybeThrow {
1153 - continuation, ..
1154 - } => {
1161 + Terminal::MaybeThrow { continuation, .. } => {
1162 let continuation_id = *continuation;
1163 let continuation_block = self.cx.block(continuation_id);
1164 let cont_instructions_empty = continuation_block.instructions.is_empty();
@@ -1161,11 +1168,7 @@ impl<'a, 'b> Driver<'a, 'b> {
1168 if cont_instructions_empty && cont_is_goto {
1169 Ok(self.extract_value_block_result(&instructions, cont_block_id, loc))
1170 } else {
1164 - let continuation = self.visit_value_block(
1165 - continuation_id,
1166 - loc,
1167 - fallthrough,
1168 - )?;
1171 + let continuation = self.visit_value_block(continuation_id, loc, fallthrough)?;
1172 Ok(self.wrap_with_sequence(&instructions, continuation, loc))
1173 }
1174 }
@@ -1239,21 +1242,22 @@ impl<'a, 'b> Driver<'a, 'b> {
1242 alternate: *alternate,
1243 branch_loc: *branch_loc,
1244 }),
1242 - other => {
1243 - Err(CompilerDiagnostic::new(
1244 - ErrorCategory::Invariant,
1245 - format!(
1246 - "Expected a branch terminal for {} test block, got {:?}",
1247 - terminal_kind,
1248 - std::mem::discriminant(other)
1249 - ),
1250 - None,
1251 - ))
1252 - }
1245 + other => Err(CompilerDiagnostic::new(
1246 + ErrorCategory::Invariant,
1247 + format!(
1248 + "Expected a branch terminal for {} test block, got {:?}",
1249 + terminal_kind,
1250 + std::mem::discriminant(other)
1251 + ),
1252 + None,
1253 + )),
1254 }
1255 }
1256
1256 - fn visit_value_block_terminal(&mut self, terminal: &Terminal) -> Result<ValueTerminalResult, CompilerDiagnostic> {
1257 + fn visit_value_block_terminal(
1258 + &mut self,
1259 + terminal: &Terminal,
1260 + ) -> Result<ValueTerminalResult, CompilerDiagnostic> {
1261 match terminal {
1262 Terminal::Sequence {
1263 block,
@@ -1277,11 +1281,8 @@ impl<'a, 'b> Driver<'a, 'b> {
1281 loc,
1282 } => {
1283 let test_result = self.visit_test_block(*test, *loc, "optional")?;
1280 - let consequent = self.visit_value_block(
1281 - test_result.consequent,
1282 - *loc,
1283 - Some(*fallthrough),
1284 - )?;
1284 + let consequent =
1285 + self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;
1286 let call = ReactiveValue::SequenceExpression {
1287 instructions: vec![ReactiveInstruction {
1288 id: test_result.test.id,
@@ -1314,11 +1315,8 @@ impl<'a, 'b> Driver<'a, 'b> {
1315 loc,
1316 } => {
1317 let test_result = self.visit_test_block(*test, *loc, "logical")?;
1317 - let left_final = self.visit_value_block(
1318 - test_result.consequent,
1319 - *loc,
1320 - Some(*fallthrough),
1321 - )?;
1318 + let left_final =
1319 + self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;
1320 let left = ReactiveValue::SequenceExpression {
1321 instructions: vec![ReactiveInstruction {
1322 id: test_result.test.id,
@@ -1331,11 +1329,8 @@ impl<'a, 'b> Driver<'a, 'b> {
1329 value: Box::new(left_final.value),
1330 loc: *loc,
1331 };
1334 - let right = self.visit_value_block(
1335 - test_result.alternate,
1336 - *loc,
1337 - Some(*fallthrough),
1338 - )?;
1332 + let right =
1333 + self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?;
1334 Ok(ValueTerminalResult {
1335 place: left_final.place,
1336 value: ReactiveValue::LogicalExpression {
@@ -1355,16 +1350,10 @@ impl<'a, 'b> Driver<'a, 'b> {
1350 loc,
1351 } => {
1352 let test_result = self.visit_test_block(*test, *loc, "ternary")?;
1358 - let consequent = self.visit_value_block(
1359 - test_result.consequent,
1360 - *loc,
1361 - Some(*fallthrough),
1362 - )?;
1363 - let alternate = self.visit_value_block(
1364 - test_result.alternate,
1365 - *loc,
1366 - Some(*fallthrough),
1367 - )?;
1353 + let consequent =
1354 + self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;
1355 + let alternate =
1356 + self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?;
1357 Ok(ValueTerminalResult {
1358 place: consequent.place,
1359 value: ReactiveValue::ConditionalExpression {
@@ -1377,27 +1366,21 @@ impl<'a, 'b> Driver<'a, 'b> {
1366 id: *id,
1367 })
1368 }
1380 - Terminal::MaybeThrow { .. } => {
1381 - Err(CompilerDiagnostic::new(
1382 - ErrorCategory::Invariant,
1383 - "Unexpected maybe-throw in visit_value_block_terminal",
1384 - None,
1385 - ))
1386 - }
1387 - Terminal::Label { .. } => {
1388 - Err(CompilerDiagnostic::new(
1389 - ErrorCategory::Todo,
1390 - "Support labeled statements combined with value blocks is not yet implemented",
1391 - None,
1392 - ))
1393 - }
1394 - _ => {
1395 - Err(CompilerDiagnostic::new(
1396 - ErrorCategory::Invariant,
1397 - "Unsupported terminal kind in value block",
1398 - None,
1399 - ))
1400 - }
1369 + Terminal::MaybeThrow { .. } => Err(CompilerDiagnostic::new(
1370 + ErrorCategory::Invariant,
1371 + "Unexpected maybe-throw in visit_value_block_terminal",
1372 + None,
1373 + )),
1374 + Terminal::Label { .. } => Err(CompilerDiagnostic::new(
1375 + ErrorCategory::Todo,
1376 + "Support labeled statements combined with value blocks is not yet implemented",
1377 + None,
1378 + )),
1379 + _ => Err(CompilerDiagnostic::new(
1380 + ErrorCategory::Invariant,
1381 + "Unsupported terminal kind in value block",
1382 + None,
1383 + )),
1384 }
1385 }
1386
@@ -1407,7 +1390,9 @@ impl<'a, 'b> Driver<'a, 'b> {
1390 block_id: BlockId,
1391 loc: Option<SourceLocation>,
1392 ) -> ValueBlockResult {
1410 - let last_id = instructions.last().expect("Expected non-empty instructions");
1393 + let last_id = instructions
1394 + .last()
1395 + .expect("Expected non-empty instructions");
1396 let last_instr = &self.hir.instructions[last_id.0 as usize];
1397
1398 let remaining: Vec<ReactiveInstruction> = instructions[..instructions.len() - 1]
@@ -1427,7 +1412,11 @@ impl<'a, 'b> Driver<'a, 'b> {
1412 // If the last instruction is a StoreLocal to a temporary (unnamed identifier),
1413 // convert it to a LoadLocal of the value being stored, matching the TS behavior.
1414 let (value, place) = match &last_instr.value {
1430 - InstructionValue::StoreLocal { lvalue, value: store_value, .. } => {
1415 + InstructionValue::StoreLocal {
1416 + lvalue,
1417 + value: store_value,
1418 + ..
1419 + } => {
1420 let ident = &self.env.identifiers[lvalue.place.identifier.0 as usize];
1421 if ident.name.is_none() {
1422 (
@@ -1589,15 +1578,17 @@ impl<'a, 'b> Driver<'a, 'b> {
1578 }
1579 return Ok(None);
1580 }
1592 - Ok(Some(ReactiveStatement::Terminal(ReactiveTerminalStatement {
1593 - terminal: ReactiveTerminal::Break {
1594 - target: target_block,
1595 - id,
1596 - target_kind,
1597 - loc,
1581 + Ok(Some(ReactiveStatement::Terminal(
1582 + ReactiveTerminalStatement {
1583 + terminal: ReactiveTerminal::Break {
1584 + target: target_block,
1585 + id,
1586 + target_kind,
1587 + loc,
1588 + },
1589 + label: None,
1590 },
1599 - label: None,
1600 - })))
1591 + )))
1592 }
1593
1594 fn visit_continue(
compiler/crates/react_compiler_reactive_scopes/src/extract_scope_declarations_from_destructuring.rs
+17 -11
@@ -11,12 +11,9 @@
11 use std::collections::HashSet;
12
13 use react_compiler_hir::{
14 - DeclarationId, IdentifierId, IdentifierName,
15 - InstructionKind, InstructionValue, LValue, ParamPattern,
16 - Place, ReactiveFunction, ReactiveInstruction, ReactiveStatement,
17 - ReactiveValue, ReactiveScopeBlock,
18 - environment::Environment,
19 - visitors,
14 + DeclarationId, IdentifierId, IdentifierName, InstructionKind, InstructionValue, LValue,
15 + ParamPattern, Place, ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock,
16 + ReactiveStatement, ReactiveValue, environment::Environment, visitors,
17 };
18
19 use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
@@ -57,9 +54,15 @@ struct Transform<'a> {
54 impl<'a> ReactiveFunctionTransform for Transform<'a> {
55 type State = ExtractState;
56
60 - fn env(&self) -> &Environment { self.env }
57 + fn env(&self) -> &Environment {
58 + self.env
59 + }
60
62 - fn visit_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut ExtractState) -> Result<(), react_compiler_diagnostics::CompilerError> {
61 + fn visit_scope(
62 + &mut self,
63 + scope: &mut ReactiveScopeBlock,
64 + state: &mut ExtractState,
65 + ) -> Result<(), react_compiler_diagnostics::CompilerError> {
66 let scope_data = &self.env.scopes[scope.scope.0 as usize];
67 let decl_ids: Vec<DeclarationId> = scope_data
68 .declarations
@@ -119,8 +122,7 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
122 }
123 // Create a temporary place (matches TS clonePlaceToTemporary)
124 let temp_id = env.next_identifier_id();
122 - let decl_id =
123 - env.identifiers[temp_id.0 as usize].declaration_id;
125 + let decl_id = env.identifiers[temp_id.0 as usize].declaration_id;
126 // Copy type from original identifier to temporary
127 let original_type = env.identifiers[place.identifier.0 as usize].type_;
128 env.identifiers[temp_id.0 as usize].type_ = original_type;
@@ -190,7 +192,11 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
192 }
193 }
194
193 -fn update_declared_from_instruction(instr: &ReactiveInstruction, env: &Environment, state: &mut ExtractState) {
195 +fn update_declared_from_instruction(
196 + instr: &ReactiveInstruction,
197 + env: &Environment,
198 + state: &mut ExtractState,
199 +) {
200 if let ReactiveValue::Instruction(iv) = &instr.value {
201 match iv {
202 InstructionValue::DeclareContext { lvalue, .. }
compiler/crates/react_compiler_reactive_scopes/src/merge_reactive_scopes_that_invalidate_together.rs
+9 -14
@@ -13,8 +13,8 @@ use std::collections::{HashMap, HashSet};
13 use react_compiler_diagnostics::CompilerError;
14 use react_compiler_hir::{
15 DeclarationId, DependencyPathEntry, EvaluationOrder, InstructionKind, InstructionValue, Place,
16 - ReactiveBlock, ReactiveFunction, ReactiveStatement, ReactiveValue,
17 - ReactiveScopeBlock, ReactiveScopeDependency, ScopeId, Type,
16 + ReactiveBlock, ReactiveFunction, ReactiveScopeBlock, ReactiveScopeDependency,
17 + ReactiveStatement, ReactiveValue, ScopeId, Type,
18 environment::Environment,
19 object_shape::{BUILT_IN_ARRAY_ID, BUILT_IN_FUNCTION_ID, BUILT_IN_JSX_ID, BUILT_IN_OBJECT_ID},
20 };
@@ -269,12 +269,12 @@ impl<'a> MergeTransform<'a> {
269 EvaluationOrder(current_range_end.0.max(next_range_end.0));
270
271 // Merge declarations from next into current
272 - let next_decls =
273 - self.env.scopes[next_scope_id.0 as usize].declarations.clone();
272 + let next_decls = self.env.scopes[next_scope_id.0 as usize]
273 + .declarations
274 + .clone();
275 for (key, value) in next_decls {
275 - let current_decls = &mut self.env.scopes
276 - [current_scope_id.0 as usize]
277 - .declarations;
276 + let current_decls =
277 + &mut self.env.scopes[current_scope_id.0 as usize].declarations;
278 if let Some(existing) =
279 current_decls.iter_mut().find(|(k, _)| *k == key)
280 {
@@ -285,11 +285,7 @@ impl<'a> MergeTransform<'a> {
285 }
286
287 // Prune declarations that are no longer used after the merged scope
288 - update_scope_declarations(
289 - current_scope_id,
290 - &self.last_usage,
291 - self.env,
292 - );
288 + update_scope_declarations(current_scope_id, &self.last_usage, self.env);
289
290 c.to = i + 1;
291 c.lvalues.clear();
@@ -480,8 +476,7 @@ fn can_merge_scopes(
476 if !dep.path.is_empty() {
477 return false;
478 }
483 - let dep_type =
484 - &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize];
479 + let dep_type = &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize];
480 if !is_always_invalidating_type(dep_type) {
481 return false;
482 }
compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs
+8 -4
@@ -56,7 +56,8 @@ impl<'a> DebugPrinter<'a> {
56 ));
57 self.fmt.line(&format!("generator: {}", func.generator));
58 self.fmt.line(&format!("is_async: {}", func.is_async));
59 - self.fmt.line(&format!("loc: {}", print::format_loc(&func.loc)));
59 + self.fmt
60 + .line(&format!("loc: {}", print::format_loc(&func.loc)));
61
62 // params
63 self.fmt.line("params:");
@@ -166,13 +167,15 @@ impl<'a> DebugPrinter<'a> {
167 self.fmt.line("effects:");
168 self.fmt.indent();
169 for (i, eff) in effects.iter().enumerate() {
169 - self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
170 + self.fmt
171 + .line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
172 }
173 self.fmt.dedent();
174 }
175 None => self.fmt.line("effects: null"),
176 }
175 - self.fmt.line(&format!("loc: {}", print::format_loc(&instr.loc)));
177 + self.fmt
178 + .line(&format!("loc: {}", print::format_loc(&instr.loc)));
179 }
180
181 // =========================================================================
@@ -188,7 +191,8 @@ impl<'a> DebugPrinter<'a> {
191 hir_formatter.map(|hf| {
192 Box::new(move |fmt: &mut PrintFormatter, func: &HirFunction| {
193 hf(fmt, func);
191 - }) as Box<dyn Fn(&mut PrintFormatter, &HirFunction) + '_>
194 + })
195 + as Box<dyn Fn(&mut PrintFormatter, &HirFunction) + '_>
196 });
197 self.fmt.format_instruction_value(
198 iv,
compiler/crates/react_compiler_reactive_scopes/src/promote_used_temporaries.rs
+2 -3
@@ -133,8 +133,8 @@ fn collect_promotable_block(
133 state.pruned.insert(
134 identifier.declaration_id,
135 PrunedInfo {
136 - active_scopes: active_scopes.clone(),
137 - used_outside_scope: false,
136 + active_scopes: active_scopes.clone(),
137 + used_outside_scope: false,
138 },
139 );
140 }
@@ -1212,4 +1212,3 @@ fn promote_identifier(identifier_id: IdentifierId, state: &mut State, env: &mut
1212 }
1213 state.promoted.insert(decl_id);
1214 }
1215 -
compiler/crates/react_compiler_reactive_scopes/src/propagate_early_returns.rs
+8 -8
@@ -13,10 +13,9 @@
13 use react_compiler_hir::{
14 BlockId, Effect, EvaluationOrder, IdentifierId, IdentifierName, InstructionKind,
15 InstructionValue, LValue, NonLocalBinding, Place, PlaceOrSpread, PrimitiveValue,
16 - PropertyLiteral, ReactiveFunction, ReactiveInstruction, ReactiveLabel,
17 - ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement, ReactiveTerminalTargetKind,
18 - ReactiveValue, ReactiveScopeBlock, ReactiveScopeDeclaration, ReactiveScopeEarlyReturn,
19 - environment::Environment,
16 + PropertyLiteral, ReactiveFunction, ReactiveInstruction, ReactiveLabel, ReactiveScopeBlock,
17 + ReactiveScopeDeclaration, ReactiveScopeEarlyReturn, ReactiveStatement, ReactiveTerminal,
18 + ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveValue, environment::Environment,
19 };
20
21 use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
@@ -198,12 +197,13 @@ fn apply_early_return_to_scope(
197 });
198
199 // Add the early return identifier as a scope declaration
201 - env.scopes[scope_id.0 as usize]
202 - .declarations
203 - .push((early_return.value, ReactiveScopeDeclaration {
200 + env.scopes[scope_id.0 as usize].declarations.push((
201 + early_return.value,
202 + ReactiveScopeDeclaration {
203 identifier: early_return.value,
204 scope: scope_id,
206 - }));
205 + },
206 + ));
207
208 // Create temporary places for the sentinel initialization
209 let sentinel_temp = create_temporary_place_id(env, loc);
compiler/crates/react_compiler_reactive_scopes/src/prune_always_invalidating_scopes.rs
+14 -9
@@ -15,18 +15,19 @@ use std::collections::HashSet;
15
16 use react_compiler_hir::{
17 IdentifierId, InstructionValue, PrunedReactiveScopeBlock, ReactiveFunction,
18 - ReactiveInstruction, ReactiveStatement, ReactiveValue, ReactiveScopeBlock,
18 + ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue,
19 environment::Environment,
20 };
21
22 -use crate::visitors::{
23 - ReactiveFunctionTransform, Transformed, transform_reactive_function,
24 -};
22 +use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
23
24 /// Prunes scopes that always invalidate because they depend on unmemoized
25 /// always-invalidating values.
26 /// TS: `pruneAlwaysInvalidatingScopes`
29 -pub fn prune_always_invalidating_scopes(func: &mut ReactiveFunction, env: &Environment) -> Result<(), react_compiler_diagnostics::CompilerError> {
27 +pub fn prune_always_invalidating_scopes(
28 + func: &mut ReactiveFunction,
29 + env: &Environment,
30 +) -> Result<(), react_compiler_diagnostics::CompilerError> {
31 let mut transform = Transform {
32 env,
33 always_invalidating_values: HashSet::new(),
@@ -45,7 +46,9 @@ struct Transform<'a> {
46 impl<'a> ReactiveFunctionTransform for Transform<'a> {
47 type State = bool; // withinScope
48
48 - fn env(&self) -> &Environment { self.env }
49 + fn env(&self) -> &Environment {
50 + self.env
51 + }
52
53 fn transform_instruction(
54 &mut self,
@@ -75,13 +78,15 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
78 lvalue: store_lvalue,
79 ..
80 }) => {
78 - if self.always_invalidating_values.contains(&store_value.identifier) {
81 + if self
82 + .always_invalidating_values
83 + .contains(&store_value.identifier)
84 + {
85 self.always_invalidating_values
86 .insert(store_lvalue.place.identifier);
87 }
88 if self.unmemoized_values.contains(&store_value.identifier) {
83 - self.unmemoized_values
84 - .insert(store_lvalue.place.identifier);
89 + self.unmemoized_values.insert(store_lvalue.place.identifier);
90 }
91 }
92 ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => {
compiler/crates/react_compiler_reactive_scopes/src/prune_hoisted_contexts.rs
+36 -25
@@ -10,13 +10,12 @@
10
11 use std::collections::HashMap;
12
13 +use react_compiler_diagnostics::{CompilerError, CompilerErrorDetail, ErrorCategory};
14 use react_compiler_hir::{
14 - EvaluationOrder, IdentifierId, InstructionKind, InstructionValue, Place,
15 - ReactiveFunction, ReactiveInstruction, ReactiveStatement,
16 - ReactiveValue, ReactiveScopeBlock,
15 + EvaluationOrder, IdentifierId, InstructionKind, InstructionValue, Place, ReactiveFunction,
16 + ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue,
17 environment::Environment,
18 };
19 -use react_compiler_diagnostics::{CompilerError, CompilerErrorDetail, ErrorCategory};
19
20 use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
21
@@ -27,7 +26,10 @@ use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive
26 /// Prunes DeclareContexts lowered for HoistedConsts and transforms any
27 /// references back to their original instruction kind.
28 /// TS: `pruneHoistedContexts`
30 -pub fn prune_hoisted_contexts(func: &mut ReactiveFunction, env: &Environment) -> Result<(), CompilerError> {
29 +pub fn prune_hoisted_contexts(
30 + func: &mut ReactiveFunction,
31 + env: &Environment,
32 +) -> Result<(), CompilerError> {
33 let mut transform = Transform { env };
34 let mut state = VisitorState {
35 active_scopes: Vec::new(),
@@ -73,13 +75,14 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
75 self.env
76 }
77
76 - fn visit_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut VisitorState) -> Result<(), CompilerError> {
78 + fn visit_scope(
79 + &mut self,
80 + scope: &mut ReactiveScopeBlock,
81 + state: &mut VisitorState,
82 + ) -> Result<(), CompilerError> {
83 let scope_data = &self.env.scopes[scope.scope.0 as usize];
78 - let decl_ids: std::collections::HashSet<IdentifierId> = scope_data
79 - .declarations
80 - .iter()
81 - .map(|(id, _)| *id)
82 - .collect();
84 + let decl_ids: std::collections::HashSet<IdentifierId> =
85 + scope_data.declarations.iter().map(|(id, _)| *id).collect();
86
87 // Add declared but not initialized variables
88 for (_, decl) in &scope_data.declarations {
@@ -111,8 +114,12 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
114 if *definition != Some(place.identifier) {
115 let mut err = CompilerError::new();
116 err.push_error_detail(
114 - CompilerErrorDetail::new(ErrorCategory::Todo, "[PruneHoistedContexts] Rewrite hoisted function references".to_string())
115 - .with_loc(place.loc)
117 + CompilerErrorDetail::new(
118 + ErrorCategory::Todo,
119 + "[PruneHoistedContexts] Rewrite hoisted function references"
120 + .to_string(),
121 + )
122 + .with_loc(place.loc),
123 );
124 return Err(err);
125 }
@@ -127,9 +134,8 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
134 state: &mut VisitorState,
135 ) -> Result<Transformed<ReactiveStatement>, CompilerError> {
136 // Remove hoisted declarations to preserve TDZ
130 - if let ReactiveValue::Instruction(InstructionValue::DeclareContext {
131 - lvalue, ..
132 - }) = &instruction.value
137 + if let ReactiveValue::Instruction(InstructionValue::DeclareContext { lvalue, .. }) =
138 + &instruction.value
139 {
140 let maybe_non_hoisted = convert_hoisted_lvalue_kind(lvalue.kind);
141 if let Some(non_hoisted) = maybe_non_hoisted {
@@ -145,16 +151,14 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
151 }
152 }
153
148 - if let ReactiveValue::Instruction(InstructionValue::StoreContext {
149 - lvalue, ..
150 - }) = &mut instruction.value
154 + if let ReactiveValue::Instruction(InstructionValue::StoreContext { lvalue, .. }) =
155 + &mut instruction.value
156 {
157 if lvalue.kind != InstructionKind::Reassign {
158 let lvalue_id = lvalue.place.identifier;
159 let is_declared_by_scope = state.find_in_active_scopes(lvalue_id);
160 if is_declared_by_scope {
156 - if lvalue.kind == InstructionKind::Let
157 - || lvalue.kind == InstructionKind::Const
161 + if lvalue.kind == InstructionKind::Let || lvalue.kind == InstructionKind::Const
162 {
163 lvalue.kind = InstructionKind::Reassign;
164 } else if lvalue.kind == InstructionKind::Function {
@@ -162,8 +166,12 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
166 if !matches!(kind, UninitializedKind::Func { .. }) {
167 let mut err = CompilerError::new();
168 err.push_error_detail(
165 - CompilerErrorDetail::new(ErrorCategory::Invariant, "[PruneHoistedContexts] Unexpected hoisted function".to_string())
166 - .with_loc(instruction.loc)
169 + CompilerErrorDetail::new(
170 + ErrorCategory::Invariant,
171 + "[PruneHoistedContexts] Unexpected hoisted function"
172 + .to_string(),
173 + )
174 + .with_loc(instruction.loc),
175 );
176 return Err(err);
177 }
@@ -174,8 +182,11 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
182 } else {
183 let mut err = CompilerError::new();
184 err.push_error_detail(
177 - CompilerErrorDetail::new(ErrorCategory::Todo, "[PruneHoistedContexts] Unexpected kind".to_string())
178 - .with_loc(instruction.loc)
185 + CompilerErrorDetail::new(
186 + ErrorCategory::Todo,
187 + "[PruneHoistedContexts] Unexpected kind".to_string(),
188 + )
189 + .with_loc(instruction.loc),
190 );
191 return Err(err);
192 }
compiler/crates/react_compiler_reactive_scopes/src/prune_non_reactive_dependencies.rs
+4 -10
@@ -12,12 +12,12 @@ use std::collections::HashSet;
12
13 use react_compiler_hir::{
14 EvaluationOrder, IdentifierId, InstructionValue, Place, PrunedReactiveScopeBlock,
15 - ReactiveFunction, ReactiveInstruction, ReactiveValue, ReactiveScopeBlock,
15 + ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveValue,
16 environment::Environment, is_primitive_type, is_use_ref_type, object_shape,
17 visitors as hir_visitors,
18 };
19
20 -use crate::visitors::{self, ReactiveFunctionVisitor, ReactiveFunctionTransform};
20 +use crate::visitors::{self, ReactiveFunctionTransform, ReactiveFunctionVisitor};
21
22 // =============================================================================
23 // CollectReactiveIdentifiers
@@ -57,11 +57,7 @@ impl<'a> ReactiveFunctionVisitor for CollectVisitor<'a> {
57 }
58 }
59
60 - fn visit_pruned_scope(
61 - &self,
62 - scope: &PrunedReactiveScopeBlock,
63 - state: &mut Self::State,
64 - ) {
60 + fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) {
61 self.traverse_pruned_scope(scope, state);
62
63 let scope_data = &self.env.scopes[scope.scope.0 as usize];
@@ -203,9 +199,7 @@ impl<'a> ReactiveFunctionTransform for PruneVisitor<'a> {
199 object, property, ..
200 }) => {
201 if let Some(lv) = lvalue {
206 - if state.contains(&object.identifier)
207 - || state.contains(&property.identifier)
208 - {
202 + if state.contains(&object.identifier) || state.contains(&property.identifier) {
203 state.insert(lv.identifier);
204 }
205 }
compiler/crates/react_compiler_reactive_scopes/src/prune_unused_labels.rs
+10 -6
@@ -11,15 +11,17 @@
11 use std::collections::HashSet;
12
13 use react_compiler_hir::{
14 - BlockId, ReactiveFunction, ReactiveStatement, ReactiveTerminal,
15 - ReactiveTerminalStatement, ReactiveTerminalTargetKind,
16 - environment::Environment,
14 + BlockId, ReactiveFunction, ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement,
15 + ReactiveTerminalTargetKind, environment::Environment,
16 };
17
19 -use crate::visitors::{transform_reactive_function, ReactiveFunctionTransform, Transformed};
18 +use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
19
20 /// Prune unused labels from a reactive function.
22 -pub fn prune_unused_labels(func: &mut ReactiveFunction, env: &Environment) -> Result<(), react_compiler_diagnostics::CompilerError> {
21 +pub fn prune_unused_labels(
22 + func: &mut ReactiveFunction,
23 + env: &Environment,
24 +) -> Result<(), react_compiler_diagnostics::CompilerError> {
25 let mut transform = Transform { env };
26 let mut labels: HashSet<BlockId> = HashSet::new();
27 transform_reactive_function(func, &mut transform, &mut labels)
@@ -32,7 +34,9 @@ struct Transform<'a> {
34 impl<'a> ReactiveFunctionTransform for Transform<'a> {
35 type State = HashSet<BlockId>;
36
35 - fn env(&self) -> &Environment { self.env }
37 + fn env(&self) -> &Environment {
38 + self.env
39 + }
40
41 fn transform_terminal(
42 &mut self,
compiler/crates/react_compiler_reactive_scopes/src/prune_unused_lvalues.rs
+3 -6
@@ -12,9 +12,8 @@
12 use std::collections::HashSet;
13
14 use react_compiler_hir::{
15 - DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction, ReactiveValue,
16 - ReactiveStatement,
17 - environment::Environment,
15 + DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction,
16 + ReactiveStatement, ReactiveValue, environment::Environment,
17 };
18
19 use crate::visitors::{self, ReactiveFunctionVisitor};
@@ -230,9 +229,7 @@ fn null_unused_in_terminal(
229 ReactiveTerminal::Label { block, .. } => {
230 null_unused_lvalues(block, env, unused);
231 }
233 - ReactiveTerminal::Try {
234 - block, handler, ..
235 - } => {
232 + ReactiveTerminal::Try { block, handler, .. } => {
233 null_unused_lvalues(block, env, unused);
234 null_unused_lvalues(handler, env, unused);
235 }
compiler/crates/react_compiler_reactive_scopes/src/prune_unused_scopes.rs
+11 -10
@@ -8,14 +8,11 @@
8 //! Corresponds to `src/ReactiveScopes/PruneUnusedScopes.ts`.
9
10 use react_compiler_hir::{
11 - PrunedReactiveScopeBlock, ReactiveFunction, ReactiveStatement, ReactiveTerminal,
12 - ReactiveTerminalStatement, ReactiveScopeBlock,
13 - environment::Environment,
11 + PrunedReactiveScopeBlock, ReactiveFunction, ReactiveScopeBlock, ReactiveStatement,
12 + ReactiveTerminal, ReactiveTerminalStatement, environment::Environment,
13 };
14
16 -use crate::visitors::{
17 - ReactiveFunctionTransform, Transformed, transform_reactive_function,
18 -};
15 +use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function};
16
17 struct State {
18 has_return_statement: bool,
@@ -23,7 +20,10 @@ struct State {
20
21 /// Converts scopes without outputs into pruned-scopes (regular blocks).
22 /// TS: `pruneUnusedScopes`
26 -pub fn prune_unused_scopes(func: &mut ReactiveFunction, env: &Environment) -> Result<(), react_compiler_diagnostics::CompilerError> {
23 +pub fn prune_unused_scopes(
24 + func: &mut ReactiveFunction,
25 + env: &Environment,
26 +) -> Result<(), react_compiler_diagnostics::CompilerError> {
27 let mut transform = Transform { env };
28 let mut state = State {
29 has_return_statement: false,
@@ -38,7 +38,9 @@ struct Transform<'a> {
38 impl<'a> ReactiveFunctionTransform for Transform<'a> {
39 type State = State;
40
41 - fn env(&self) -> &Environment { self.env }
41 + fn env(&self) -> &Environment {
42 + self.env
43 + }
44
45 fn visit_terminal(
46 &mut self,
@@ -67,8 +69,7 @@ impl<'a> ReactiveFunctionTransform for Transform<'a> {
69
70 if !scope_state.has_return_statement
71 && scope_data.reassignments.is_empty()
70 - && (scope_data.declarations.is_empty()
71 - || !has_own_declaration(scope_data, scope_id))
72 + && (scope_data.declarations.is_empty() || !has_own_declaration(scope_data, scope_id))
73 {
74 // Replace with pruned scope
75 Ok(Transformed::Replace(ReactiveStatement::PrunedScope(
compiler/crates/react_compiler_reactive_scopes/src/stabilize_block_ids.rs
+11 -17
@@ -14,15 +14,13 @@ use std::collections::HashMap;
14
15 use indexmap::IndexSet;
16 use react_compiler_hir::{
17 - BlockId, ReactiveFunction,
18 - ReactiveTerminal, ReactiveTerminalStatement,
19 - ReactiveScopeBlock,
17 + BlockId, ReactiveFunction, ReactiveScopeBlock, ReactiveTerminal, ReactiveTerminalStatement,
18 environment::Environment,
19 };
20
21 use crate::visitors::{
24 - ReactiveFunctionVisitor, visit_reactive_function,
25 - ReactiveFunctionTransform, transform_reactive_function,
22 + ReactiveFunctionTransform, ReactiveFunctionVisitor, transform_reactive_function,
23 + visit_reactive_function,
24 };
25
26 /// Rewrites block IDs to sequential values.
@@ -56,13 +54,11 @@ struct CollectReferencedLabels<'a> {
54 impl<'a> ReactiveFunctionVisitor for CollectReferencedLabels<'a> {
55 type State = IndexSet<BlockId>;
56
59 - fn env(&self) -> &Environment { self.env }
57 + fn env(&self) -> &Environment {
58 + self.env
59 + }
60
61 - fn visit_scope(
62 - &self,
63 - scope: &ReactiveScopeBlock,
64 - state: &mut Self::State,
65 - ) {
61 + fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) {
62 let scope_data = &self.env.scopes[scope.scope.0 as usize];
63 if let Some(ref early_return) = scope_data.early_return_value {
64 state.insert(early_return.label);
@@ -70,11 +66,7 @@ impl<'a> ReactiveFunctionVisitor for CollectReferencedLabels<'a> {
66 self.traverse_scope(scope, state);
67 }
68
73 - fn visit_terminal(
74 - &self,
75 - stmt: &ReactiveTerminalStatement,
76 - state: &mut Self::State,
77 - ) {
69 + fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) {
70 if let Some(ref label) = stmt.label {
71 if !label.implicit {
72 state.insert(label.id);
@@ -101,7 +93,9 @@ struct RewriteBlockIds<'a> {
93 impl<'a> ReactiveFunctionTransform for RewriteBlockIds<'a> {
94 type State = HashMap<BlockId, BlockId>;
95
104 - fn env(&self) -> &Environment { self.env }
96 + fn env(&self) -> &Environment {
97 + self.env
98 + }
99
100 fn visit_scope(
101 &mut self,
compiler/crates/react_compiler_reactive_scopes/src/visitors.rs
+59 -38
@@ -9,11 +9,9 @@
9
10 use react_compiler_diagnostics::CompilerError;
11 use react_compiler_hir::{
12 - EvaluationOrder, FunctionId, InstructionValue, ParamPattern, Place,
13 - PrunedReactiveScopeBlock, ReactiveBlock, ReactiveFunction, ReactiveInstruction,
14 - ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement, ReactiveValue,
15 - ReactiveScopeBlock,
16 - environment::Environment,
12 + EvaluationOrder, FunctionId, InstructionValue, ParamPattern, Place, PrunedReactiveScopeBlock,
13 + ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement,
14 + ReactiveTerminal, ReactiveTerminalStatement, ReactiveValue, environment::Environment,
15 };
16
17 // =============================================================================
@@ -125,7 +123,10 @@ pub trait ReactiveFunctionVisitor {
123 self.visit_value(*seq_id, inner, state);
124 }
125 ReactiveValue::Instruction(instr_value) => {
128 - let operands = react_compiler_hir::visitors::each_instruction_value_operand(instr_value, self.env());
126 + let operands = react_compiler_hir::visitors::each_instruction_value_operand(
127 + instr_value,
128 + self.env(),
129 + );
130 for place in &operands {
131 self.visit_place(id, place, state);
132 }
@@ -152,19 +153,11 @@ pub trait ReactiveFunctionVisitor {
153 self.visit_value(instruction.id, &instruction.value, state);
154 }
155
155 - fn visit_terminal(
156 - &self,
157 - stmt: &ReactiveTerminalStatement,
158 - state: &mut Self::State,
159 - ) {
156 + fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) {
157 self.traverse_terminal(stmt, state);
158 }
159
163 - fn traverse_terminal(
164 - &self,
165 - stmt: &ReactiveTerminalStatement,
166 - state: &mut Self::State,
167 - ) {
160 + fn traverse_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) {
161 let terminal = &stmt.terminal;
162 let id = terminal_id(terminal);
163 self.visit_id(id, state);
@@ -282,19 +275,11 @@ pub trait ReactiveFunctionVisitor {
275 self.visit_block(&scope.instructions, state);
276 }
277
285 - fn visit_pruned_scope(
286 - &self,
287 - scope: &PrunedReactiveScopeBlock,
288 - state: &mut Self::State,
289 - ) {
278 + fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) {
279 self.traverse_pruned_scope(scope, state);
280 }
281
293 - fn traverse_pruned_scope(
294 - &self,
295 - scope: &PrunedReactiveScopeBlock,
296 - state: &mut Self::State,
297 - ) {
282 + fn traverse_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) {
283 self.visit_block(&scope.instructions, state);
284 }
285
@@ -372,11 +357,31 @@ pub trait ReactiveFunctionTransform {
357 /// TS `eachInstructionValueOperand` behavior).
358 fn env(&self) -> &Environment;
359
375 - fn visit_id(&mut self, _id: EvaluationOrder, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
360 + fn visit_id(
361 + &mut self,
362 + _id: EvaluationOrder,
363 + _state: &mut Self::State,
364 + ) -> Result<(), CompilerError> {
365 + Ok(())
366 + }
367
377 - fn visit_place(&mut self, _id: EvaluationOrder, _place: &Place, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
368 + fn visit_place(
369 + &mut self,
370 + _id: EvaluationOrder,
371 + _place: &Place,
372 + _state: &mut Self::State,
373 + ) -> Result<(), CompilerError> {
374 + Ok(())
375 + }
376
379 - fn visit_lvalue(&mut self, _id: EvaluationOrder, _lvalue: &Place, _state: &mut Self::State) -> Result<(), CompilerError> { Ok(()) }
377 + fn visit_lvalue(
378 + &mut self,
379 + _id: EvaluationOrder,
380 + _lvalue: &Place,
381 + _state: &mut Self::State,
382 + ) -> Result<(), CompilerError> {
383 + Ok(())
384 + }
385
386 fn visit_value(
387 &mut self,
@@ -447,7 +452,10 @@ pub trait ReactiveFunctionTransform {
452 ReactiveValue::Instruction(instr_value) => {
453 // Collect operands before visiting to avoid borrow conflict
454 // (self.env() borrows self immutably, self.visit_place() needs &mut self).
450 - let operands = react_compiler_hir::visitors::each_instruction_value_operand(instr_value, self.env());
455 + let operands = react_compiler_hir::visitors::each_instruction_value_operand(
456 + instr_value,
457 + self.env(),
458 + );
459 for place in &operands {
460 self.visit_place(id, place, state)?;
461 }
@@ -651,11 +659,19 @@ pub trait ReactiveFunctionTransform {
659 Ok(())
660 }
661
654 - fn visit_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut Self::State) -> Result<(), CompilerError> {
662 + fn visit_scope(
663 + &mut self,
664 + scope: &mut ReactiveScopeBlock,
665 + state: &mut Self::State,
666 + ) -> Result<(), CompilerError> {
667 self.traverse_scope(scope, state)
668 }
669
658 - fn traverse_scope(&mut self, scope: &mut ReactiveScopeBlock, state: &mut Self::State) -> Result<(), CompilerError> {
670 + fn traverse_scope(
671 + &mut self,
672 + scope: &mut ReactiveScopeBlock,
673 + state: &mut Self::State,
674 + ) -> Result<(), CompilerError> {
675 self.visit_block(&mut scope.instructions, state)
676 }
677
@@ -675,7 +691,11 @@ pub trait ReactiveFunctionTransform {
691 self.visit_block(&mut scope.instructions, state)
692 }
693
678 - fn visit_block(&mut self, block: &mut ReactiveBlock, state: &mut Self::State) -> Result<(), CompilerError> {
694 + fn visit_block(
695 + &mut self,
696 + block: &mut ReactiveBlock,
697 + state: &mut Self::State,
698 + ) -> Result<(), CompilerError> {
699 self.traverse_block(block, state)
700 }
701
@@ -715,7 +735,11 @@ pub trait ReactiveFunctionTransform {
735 Ok(Transformed::Keep)
736 }
737
718 - fn traverse_block(&mut self, block: &mut ReactiveBlock, state: &mut Self::State) -> Result<(), CompilerError> {
738 + fn traverse_block(
739 + &mut self,
740 + block: &mut ReactiveBlock,
741 + state: &mut Self::State,
742 + ) -> Result<(), CompilerError> {
743 let mut next_block: Option<Vec<ReactiveStatement>> = None;
744 let len = block.len();
745 for i in 0..len {
@@ -737,9 +761,7 @@ pub trait ReactiveFunctionTransform {
761 ReactiveStatement::Instruction(instr) => {
762 self.transform_instruction(instr, state)?
763 }
740 - ReactiveStatement::Scope(scope) => {
741 - self.transform_scope(scope, state)?
742 - }
764 + ReactiveStatement::Scope(scope) => self.transform_scope(scope, state)?,
765 ReactiveStatement::PrunedScope(scope) => {
766 self.transform_pruned_scope(scope, state)?
767 }
@@ -813,4 +835,3 @@ fn terminal_id(terminal: &ReactiveTerminal) -> EvaluationOrder {
835 | ReactiveTerminal::Try { id, .. } => *id,
836 }
837 }
816 -
compiler/crates/react_compiler_ssa/src/eliminate_redundant_phi.rs
+9 -12
@@ -16,7 +16,6 @@ fn rewrite_place(place: &mut Place, rewrites: &HashMap<IdentifierId, IdentifierI
16 }
17 }
18
19 -
19 // =============================================================================
20 // Public entry point
21 // =============================================================================
@@ -93,12 +92,8 @@ fn eliminate_redundant_phi_impl(
92 });
93
94 // Rewrite instructions
96 - let instruction_ids: Vec<InstructionId> = ir
97 - .blocks
98 - .get(&block_id)
99 - .unwrap()
100 - .instructions
101 - .clone();
95 + let instruction_ids: Vec<InstructionId> =
96 + ir.blocks.get(&block_id).unwrap().instructions.clone();
97
98 for instr_id in &instruction_ids {
99 let instr_idx = instr_id.0 as usize;
@@ -111,9 +106,12 @@ fn eliminate_redundant_phi_impl(
106 });
107
108 // Rewrite operands using canonical visitor
114 - visitors::for_each_instruction_value_operand_mut(&mut func.instructions[instr_idx].value, &mut |place| {
115 - rewrite_place(place, rewrites);
116 - });
109 + visitors::for_each_instruction_value_operand_mut(
110 + &mut func.instructions[instr_idx].value,
111 + &mut |place| {
112 + rewrite_place(place, rewrites);
113 + },
114 + );
115
116 // Handle FunctionExpression/ObjectMethod context and recursion
117 let instr = &func.instructions[instr_idx];
@@ -127,8 +125,7 @@ fn eliminate_redundant_phi_impl(
125
126 if let Some(fid) = func_expr_id {
127 // Rewrite context places
130 - let context =
131 - &mut env.functions[fid.0 as usize].context;
128 + let context = &mut env.functions[fid.0 as usize].context;
129 for place in context.iter_mut() {
130 rewrite_place(place, rewrites);
131 }
compiler/crates/react_compiler_ssa/src/enter_ssa.rs
+26 -33
@@ -3,8 +3,8 @@ use std::collections::{HashMap, HashSet};
3 use indexmap::IndexMap;
4 use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory};
5 use react_compiler_hir::environment::Environment;
6 -use react_compiler_hir::*;
6 use react_compiler_hir::visitors;
7 +use react_compiler_hir::*;
8
9 // =============================================================================
10 // SSABuilder
@@ -57,7 +57,9 @@ impl SSABuilder {
57 }
58
59 fn state_mut(&mut self) -> &mut State {
60 - let current = self.current.expect("we need to be in a block to access state!");
60 + let current = self
61 + .current
62 + .expect("we need to be in a block to access state!");
63 self.states
64 .get_mut(&current)
65 .expect("state not found for current block")
@@ -76,7 +78,11 @@ impl SSABuilder {
78 new_id
79 }
80
79 - fn define_place(&mut self, old_place: &Place, env: &mut Environment) -> Result<Place, CompilerDiagnostic> {
81 + fn define_place(
82 + &mut self,
83 + old_place: &Place,
84 + env: &mut Environment,
85 + ) -> Result<Place, CompilerDiagnostic> {
86 let old_id = old_place.identifier;
87
88 if self.unknown.contains(&old_id) {
@@ -89,7 +95,8 @@ impl SSABuilder {
95 ErrorCategory::Todo,
96 "[hoisting] EnterSSA: Expected identifier to be defined before being used",
97 Some(format!("Identifier {} is undefined", name)),
92 - ).with_detail(CompilerDiagnosticDetail::Error {
98 + )
99 + .with_detail(CompilerDiagnosticDetail::Error {
100 loc: old_place.loc,
101 message: None,
102 identifier_name: None,
@@ -112,7 +119,11 @@ impl SSABuilder {
119 }
120
121 #[allow(dead_code)]
115 - fn define_context(&mut self, old_place: &Place, env: &mut Environment) -> Result<Place, CompilerDiagnostic> {
122 + fn define_context(
123 + &mut self,
124 + old_place: &Place,
125 + env: &mut Environment,
126 + ) -> Result<Place, CompilerDiagnostic> {
127 let old_id = old_place.identifier;
128 let new_place = self.define_place(old_place, env)?;
129 self.context.insert(old_id);
@@ -154,11 +165,7 @@ impl SSABuilder {
165 }
166 }
167
157 - let preds = self
158 - .block_preds
159 - .get(&block_id)
160 - .cloned()
161 - .unwrap_or_default();
168 + let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default();
169
170 if preds.is_empty() {
171 self.unknown.insert(old_place.identifier);
@@ -217,11 +224,7 @@ impl SSABuilder {
224 new_place: &Place,
225 env: &mut Environment,
226 ) {
220 - let preds = self
221 - .block_preds
222 - .get(&block_id)
223 - .cloned()
224 - .unwrap_or_default();
227 + let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default();
228
229 let mut pred_defs: IndexMap<BlockId, Place> = IndexMap::new();
230 for pred_block_id in &preds {
@@ -242,10 +245,7 @@ impl SSABuilder {
245 operands: pred_defs,
246 };
247
245 - self.pending_phis
246 - .entry(block_id)
247 - .or_default()
248 - .push(phi);
248 + self.pending_phis.entry(block_id).or_default().push(phi);
249 }
250
251 fn fix_incomplete_phis(&mut self, block_id: BlockId, env: &mut Environment) {
@@ -277,10 +277,7 @@ impl SSABuilder {
277 // Public entry point
278 // =============================================================================
279
280 -pub fn enter_ssa(
281 - func: &mut HirFunction,
282 - env: &mut Environment,
283 -) -> Result<(), CompilerDiagnostic> {
280 +pub fn enter_ssa(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
281 let mut builder = SSABuilder::new(&func.body.blocks);
282 let root_entry = func.body.entry;
283 enter_ssa_impl(func, &mut builder, env, root_entry)?;
@@ -291,11 +288,7 @@ pub fn enter_ssa(
288 Ok(())
289 }
290
294 -fn apply_pending_phis(
295 - func: &mut HirFunction,
296 - env: &mut Environment,
297 - builder: &mut SSABuilder,
298 -) {
291 +fn apply_pending_phis(func: &mut HirFunction, env: &mut Environment, builder: &mut SSABuilder) {
292 for (block_id, block) in func.body.blocks.iter_mut() {
293 if let Some(phis) = builder.pending_phis.remove(block_id) {
294 block.phis.extend(phis);
@@ -442,7 +435,9 @@ fn enter_ssa_impl(
435 let mut new_inner_params = Vec::with_capacity(inner_params.len());
436 for param in inner_params {
437 new_inner_params.push(match param {
445 - ParamPattern::Place(p) => ParamPattern::Place(builder.define_place(&p, env)?),
438 + ParamPattern::Place(p) => {
439 + ParamPattern::Place(builder.define_place(&p, env)?)
440 + }
441 ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern {
442 place: builder.define_place(&s.place, env)?,
443 }),
@@ -451,10 +446,8 @@ fn enter_ssa_impl(
446 env.functions[fid.0 as usize].params = new_inner_params;
447
448 // Take the inner function out of the arena to process it
454 - let mut inner_func = std::mem::replace(
455 - &mut env.functions[fid.0 as usize],
456 - placeholder_function(),
457 - );
449 + let mut inner_func =
450 + std::mem::replace(&mut env.functions[fid.0 as usize], placeholder_function());
451
452 enter_ssa_impl(&mut inner_func, builder, env, root_entry)?;
453
compiler/crates/react_compiler_ssa/src/lib.rs
+2 -2
@@ -1,7 +1,7 @@
1 -pub mod enter_ssa;
1 mod eliminate_redundant_phi;
2 +pub mod enter_ssa;
3 mod rewrite_instruction_kinds_based_on_reassignment;
4
5 -pub use enter_ssa::enter_ssa;
5 pub use eliminate_redundant_phi::eliminate_redundant_phi;
6 +pub use enter_ssa::enter_ssa;
7 pub use rewrite_instruction_kinds_based_on_reassignment::rewrite_instruction_kinds_based_on_reassignment;
compiler/crates/react_compiler_ssa/src/rewrite_instruction_kinds_based_on_reassignment.rs
+23 -26
@@ -18,14 +18,12 @@
18 use std::collections::HashMap;
19
20 use react_compiler_diagnostics::{
21 - CompilerDiagnostic, CompilerDiagnosticDetail,
22 - CompilerError, ErrorCategory, SourceLocation,
21 + CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, SourceLocation,
22 };
23 +use react_compiler_hir::visitors::each_pattern_operand;
24 use react_compiler_hir::{
25 - BlockKind, DeclarationId, HirFunction, InstructionKind, InstructionValue, ParamPattern,
26 - Place,
25 + BlockKind, DeclarationId, HirFunction, InstructionKind, InstructionValue, ParamPattern, Place,
26 };
28 -use react_compiler_hir::visitors::each_pattern_operand;
27
28 use react_compiler_hir::environment::Environment;
29
@@ -36,17 +34,18 @@ fn invariant_error(reason: &str, description: Option<String>) -> CompilerError {
34 invariant_error_with_loc(reason, description, None)
35 }
36
39 -fn invariant_error_with_loc(reason: &str, description: Option<String>, loc: Option<SourceLocation>) -> CompilerError {
37 +fn invariant_error_with_loc(
38 + reason: &str,
39 + description: Option<String>,
40 + loc: Option<SourceLocation>,
41 +) -> CompilerError {
42 let mut err = CompilerError::new();
41 - let diagnostic = CompilerDiagnostic::new(
42 - ErrorCategory::Invariant,
43 - reason,
44 - description,
45 - ).with_detail(CompilerDiagnosticDetail::Error {
46 - loc,
47 - message: Some(reason.to_string()),
48 - identifier_name: None,
49 - });
43 + let diagnostic = CompilerDiagnostic::new(ErrorCategory::Invariant, reason, description)
44 + .with_detail(CompilerDiagnosticDetail::Error {
45 + loc,
46 + message: Some(reason.to_string()),
47 + identifier_name: None,
48 + });
49 err.push_diagnostic(diagnostic);
50 err
51 }
@@ -78,7 +77,10 @@ fn format_place(place: &Place, env: &Environment) -> String {
77 None => String::new(),
78 };
79 let mutable_range = if ident.mutable_range.end.0 > ident.mutable_range.start.0 + 1 {
81 - format!("[{}:{}]", ident.mutable_range.start.0, ident.mutable_range.end.0)
80 + format!(
81 + "[{}:{}]",
82 + ident.mutable_range.start.0, ident.mutable_range.end.0
83 + )
84 } else {
85 String::new()
86 };
@@ -152,7 +154,8 @@ pub fn rewrite_instruction_kinds_based_on_reassignment(
154 let instr = &func.instructions[instr_id.0 as usize];
155 match &instr.value {
156 InstructionValue::DeclareLocal { lvalue, .. } => {
155 - let decl_id = env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;
157 + let decl_id =
158 + env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;
159 if declarations.contains_key(&decl_id) {
160 return Err(invariant_error_with_loc(
161 "Expected variable not to be defined prior to declaration",
@@ -288,10 +291,8 @@ pub fn rewrite_instruction_kinds_based_on_reassignment(
291 }
292 }
293 }
291 - let kind = kind.ok_or_else(|| invariant_error(
292 - "Expected at least one operand",
293 - None,
294 - ))?;
294 + let kind =
295 + kind.ok_or_else(|| invariant_error("Expected at least one operand", None))?;
296 destructure_kind_locs.push((block_index, local_idx, kind));
297 }
298 InstructionValue::PostfixUpdate { lvalue, .. }
@@ -301,10 +302,7 @@ pub fn rewrite_instruction_kinds_based_on_reassignment(
302 let Some(existing) = declarations.get(&decl_id) else {
303 return Err(invariant_error_with_loc(
304 "Expected variable to have been defined",
304 - Some(format!(
305 - "No declaration for {}",
306 - format_place(lvalue, env),
307 - )),
305 + Some(format!("No declaration for {}", format_place(lvalue, env),)),
306 lvalue.loc,
307 ));
308 };
@@ -389,4 +387,3 @@ pub fn rewrite_instruction_kinds_based_on_reassignment(
387
388 Ok(())
389 }
392 -
compiler/crates/react_compiler_swc/Cargo.toml deleted
-19
@@ -1,19 +0,0 @@
1 -[package]
2 -name = "react_compiler_swc"
3 -version = "0.1.0"
4 -edition = "2024"
5 -
6 -[dependencies]
7 -react_compiler_ast = { path = "../react_compiler_ast" }
8 -react_compiler = { path = "../react_compiler" }
9 -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" }
10 -react_compiler_hir = { path = "../react_compiler_hir" }
11 -swc_ecma_ast = "21"
12 -swc_ecma_visit = "21"
13 -swc_common = "19"
14 -swc_ecma_parser = "35"
15 -swc_ecma_codegen = "24"
16 -swc_atoms = "9"
17 -indexmap = { version = "2", features = ["serde"] }
18 -serde = { version = "1", features = ["derive"] }
19 -serde_json = "1"
compiler/crates/react_compiler_swc/src/apply_renames.rs deleted
-133
@@ -1,133 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use std::collections::HashMap;
7 -
8 -use react_compiler::entrypoint::compile_result::BindingRenameInfo;
9 -use react_compiler_ast::scope::BindingId;
10 -use swc_atoms::Atom;
11 -use swc_ecma_ast::*;
12 -use swc_ecma_visit::{VisitMut, VisitMutWith};
13 -
14 -use crate::convert_scope::build_scope_info;
15 -
16 -pub fn apply_renames(module: &mut Module, renames: &[BindingRenameInfo]) {
17 - if renames.is_empty() {
18 - return;
19 - }
20 -
21 - let scope_info = build_scope_info(module);
22 - let renames_by_declaration: HashMap<u32, &BindingRenameInfo> = renames
23 - .iter()
24 - .map(|rename| (rename.declaration_start, rename))
25 - .collect();
26 - let mut renamed_bindings: HashMap<BindingId, String> = HashMap::new();
27 -
28 - for binding in &scope_info.bindings {
29 - let Some(rename) = binding
30 - .declaration_start
31 - .and_then(|start| renames_by_declaration.get(&start))
32 - else {
33 - continue;
34 - };
35 - if binding.name == rename.original {
36 - renamed_bindings.insert(binding.id, rename.renamed.clone());
37 - }
38 - }
39 -
40 - if renamed_bindings.is_empty() {
41 - return;
42 - }
43 -
44 - let rewrite_plan: HashMap<u32, String> = scope_info
45 - .reference_to_binding
46 - .iter()
47 - .filter_map(|(&position, binding_id)| {
48 - renamed_bindings.get(binding_id).map(|renamed| (position, renamed.clone()))
49 - })
50 - .collect();
51 -
52 - module.visit_mut_with(&mut RenameApplyVisitor { rewrite_plan });
53 -}
54 -
55 -struct RenameApplyVisitor {
56 - rewrite_plan: HashMap<u32, String>,
57 -}
58 -
59 -impl RenameApplyVisitor {
60 - fn renamed_at(&self, position: u32) -> Option<String> {
61 - self.rewrite_plan.get(&position).cloned()
62 - }
63 -}
64 -
65 -impl VisitMut for RenameApplyVisitor {
66 - fn visit_mut_ident(&mut self, ident: &mut Ident) {
67 - if let Some(renamed) = self.renamed_at(ident.span.lo.0) {
68 - ident.sym = Atom::from(renamed);
69 - }
70 - }
71 -
72 - fn visit_mut_member_expr(&mut self, member: &mut MemberExpr) {
73 - member.obj.visit_mut_with(self);
74 - if let MemberProp::Computed(computed) = &mut member.prop {
75 - computed.visit_mut_with(self);
76 - }
77 - }
78 -
79 - fn visit_mut_prop(&mut self, prop: &mut Prop) {
80 - match prop {
81 - Prop::Shorthand(ident) => {
82 - if let Some(renamed) = self.renamed_at(ident.span.lo.0) {
83 - let mut value = ident.clone();
84 - value.sym = Atom::from(renamed);
85 - // Shorthand `{ref}` must become `{ref: ref_0}` to preserve property semantics.
86 - *prop = Prop::KeyValue(KeyValueProp {
87 - key: PropName::Ident(IdentName {
88 - span: ident.span,
89 - sym: ident.sym.clone(),
90 - }),
91 - value: Box::new(Expr::Ident(value)),
92 - });
93 - }
94 - }
95 - Prop::Assign(assign) => {
96 - assign.value.visit_mut_with(self);
97 - }
98 - _ => prop.visit_mut_children_with(self),
99 - }
100 - }
101 -
102 - fn visit_mut_object_pat_prop(&mut self, prop: &mut ObjectPatProp) {
103 - match prop {
104 - ObjectPatProp::Assign(assign) => {
105 - if let Some(value) = &mut assign.value {
106 - value.visit_mut_with(self);
107 - }
108 -
109 - if let Some(renamed) = self.renamed_at(assign.key.id.span.lo.0) {
110 - let mut binding = assign.key.clone();
111 - binding.id.sym = Atom::from(renamed);
112 - let value = match assign.value.take() {
113 - Some(default_value) => Pat::Assign(AssignPat {
114 - span: assign.span,
115 - left: Box::new(Pat::Ident(binding)),
116 - right: default_value,
117 - }),
118 - None => Pat::Ident(binding),
119 - };
120 -
121 - *prop = ObjectPatProp::KeyValue(KeyValuePatProp {
122 - key: PropName::Ident(IdentName {
123 - span: assign.key.id.span,
124 - sym: assign.key.id.sym.clone(),
125 - }),
126 - value: Box::new(value),
127 - });
128 - }
129 - }
130 - _ => prop.visit_mut_children_with(self),
131 - }
132 - }
133 -}
compiler/crates/react_compiler_swc/src/convert_ast.rs deleted
-2356
@@ -1,2356 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use react_compiler_ast::File;
7 -use react_compiler_ast::Program;
8 -use react_compiler_ast::SourceType;
9 -use react_compiler_ast::common::BaseNode;
10 -use react_compiler_ast::common::RawNode;
11 -use react_compiler_ast::common::Position;
12 -use react_compiler_ast::common::SourceLocation;
13 -use react_compiler_ast::declarations::*;
14 -use react_compiler_ast::expressions::*;
15 -use react_compiler_ast::jsx::*;
16 -use react_compiler_ast::literals::*;
17 -use react_compiler_ast::operators::*;
18 -use react_compiler_ast::patterns::*;
19 -use react_compiler_ast::statements::*;
20 -use swc_common::Span;
21 -use swc_common::Spanned;
22 -use swc_ecma_ast as swc;
23 -
24 -/// Helper to convert SWC's Wtf8Atom (which doesn't impl Display) to a String.
25 -fn wtf8_to_string(value: &swc_atoms::Wtf8Atom) -> String {
26 - value.to_string_lossy().into_owned()
27 -}
28 -
29 -/// Wrap a raw Babel-shaped JSON node in [`Statement::Unknown`]. The caller
30 -/// constructs `raw` with a `type` tag, so `from_raw` cannot fail.
31 -fn unknown_statement(raw: serde_json::Value) -> Statement {
32 - Statement::Unknown(
33 - UnknownStatement::from_raw(RawNode::from_value(&raw))
34 - .expect("raw unknown node is constructed with a `type` tag"),
35 - )
36 -}
37 -
38 -/// Converts an SWC Module AST to the React compiler's Babel-compatible AST.
39 -pub fn convert_module(module: &swc::Module, source_text: &str) -> File {
40 - convert_module_with_source_type(module, source_text, SourceType::Module)
41 -}
42 -
43 -/// Converts an SWC Module AST to the React compiler's Babel-compatible AST
44 -/// with an explicit source type.
45 -pub fn convert_module_with_source_type(
46 - module: &swc::Module,
47 - source_text: &str,
48 - source_type: SourceType,
49 -) -> File {
50 - let ctx = ConvertCtx::new(source_text);
51 - let base = ctx.make_base_node(module.span);
52 -
53 - let mut body: Vec<Statement> = Vec::new();
54 - let mut directives: Vec<Directive> = Vec::new();
55 - let mut past_directives = false;
56 -
57 - for item in &module.body {
58 - if !past_directives {
59 - if let Some(dir) = try_extract_directive(item, &ctx) {
60 - directives.push(dir);
61 - continue;
62 - }
63 - past_directives = true;
64 - }
65 - body.push(ctx.convert_module_item(item));
66 - }
67 -
68 - // Extract comments from source text for suppression detection
69 - let comments = extract_comments_from_source(source_text);
70 -
71 - File {
72 - base: ctx.make_base_node(module.span),
73 - program: Program {
74 - base,
75 - body,
76 - directives,
77 - source_type,
78 - interpreter: None,
79 - source_file: None,
80 - },
81 - comments,
82 - errors: vec![],
83 - }
84 -}
85 -
86 -/// Extract comments from source text for suppression detection.
87 -/// This uses simple regex-style parsing to find block and line comments.
88 -fn extract_comments_from_source(source: &str) -> Vec<react_compiler_ast::common::Comment> {
89 - use react_compiler_ast::common::Comment;
90 - use react_compiler_ast::common::CommentData;
91 - let mut comments = Vec::new();
92 - let bytes = source.as_bytes();
93 - let len = bytes.len();
94 - let mut i = 0;
95 - let mut line_offsets = vec![0u32];
96 - for (i, ch) in source.char_indices() {
97 - if ch == '\n' {
98 - line_offsets.push((i + 1) as u32);
99 - }
100 - }
101 - let position = |offset: u32| {
102 - let line_idx = match line_offsets.binary_search(&offset) {
103 - Ok(idx) => idx,
104 - Err(idx) => idx.saturating_sub(1),
105 - };
106 - let line_start = line_offsets[line_idx];
107 - Position {
108 - line: (line_idx as u32) + 1,
109 - column: offset - line_start,
110 - index: Some(offset),
111 - }
112 - };
113 - let source_location = |start: u32, end: u32| SourceLocation {
114 - start: position(start),
115 - end: position(end),
116 - filename: None,
117 - identifier_name: None,
118 - };
119 -
120 - while i < len {
121 - if bytes[i] == b'/' && i + 1 < len {
122 - if bytes[i + 1] == b'/' {
123 - // Line comment
124 - let start = i as u32;
125 - let content_start = i + 2;
126 - let mut end = content_start;
127 - while end < len && bytes[end] != b'\n' {
128 - end += 1;
129 - }
130 - let value = String::from_utf8_lossy(&bytes[content_start..end]).to_string();
131 - let end_u32 = end as u32;
132 - comments.push(Comment::CommentLine(CommentData {
133 - value: value.trim().to_string(),
134 - start: Some(start),
135 - end: Some(end_u32),
136 - loc: Some(source_location(start, end_u32)),
137 - }));
138 - i = end;
139 - continue;
140 - } else if bytes[i + 1] == b'*' {
141 - // Block comment
142 - let start = i as u32;
143 - let content_start = i + 2;
144 - let mut end = content_start;
145 - while end + 1 < len {
146 - if bytes[end] == b'*' && bytes[end + 1] == b'/' {
147 - break;
148 - }
149 - end += 1;
150 - }
151 - let value = String::from_utf8_lossy(&bytes[content_start..end]).to_string();
152 - let comment_end = if end + 1 < len { end + 2 } else { end };
153 - let comment_end_u32 = comment_end as u32;
154 - comments.push(Comment::CommentBlock(CommentData {
155 - value: value.trim().to_string(),
156 - start: Some(start),
157 - end: Some(comment_end_u32),
158 - loc: Some(source_location(start, comment_end_u32)),
159 - }));
160 - i = comment_end;
161 - continue;
162 - }
163 - }
164 - // Skip string literals to avoid matching // inside strings
165 - if bytes[i] == b'"' || bytes[i] == b'\'' || bytes[i] == b'`' {
166 - let quote = bytes[i];
167 - i += 1;
168 - while i < len {
169 - if bytes[i] == b'\\' {
170 - i += 2; // skip escaped char
171 - continue;
172 - }
173 - if bytes[i] == quote {
174 - break;
175 - }
176 - i += 1;
177 - }
178 - }
179 - i += 1;
180 - }
181 -
182 - comments
183 -}
184 -
185 -fn try_extract_directive(item: &swc::ModuleItem, ctx: &ConvertCtx) -> Option<Directive> {
186 - if let swc::ModuleItem::Stmt(swc::Stmt::Expr(expr_stmt)) = item {
187 - if let swc::Expr::Lit(swc::Lit::Str(s)) = &*expr_stmt.expr {
188 - return Some(Directive {
189 - base: ctx.make_base_node(expr_stmt.span),
190 - value: DirectiveLiteral {
191 - base: ctx.make_base_node(s.span),
192 - value: wtf8_to_string(&s.value),
193 - },
194 - });
195 - }
196 - }
197 - None
198 -}
199 -
200 -struct ConvertCtx<'a> {
201 - #[allow(dead_code)]
202 - source_text: &'a str,
203 - line_offsets: Vec<u32>,
204 - utf16_offsets: Vec<u32>,
205 -}
206 -
207 -impl<'a> ConvertCtx<'a> {
208 - fn new(source_text: &'a str) -> Self {
209 - let mut line_offsets = vec![0u32];
210 - let mut utf16_offsets = vec![0u32; source_text.len() + 1];
211 - let mut utf16_offset = 0u32;
212 - for (i, ch) in source_text.char_indices() {
213 - let next = i + ch.len_utf8();
214 - utf16_offsets[i..next].fill(utf16_offset);
215 - utf16_offset += ch.len_utf16() as u32;
216 - if ch == '\n' {
217 - line_offsets.push(next as u32);
218 - }
219 - }
220 - utf16_offsets[source_text.len()] = utf16_offset;
221 - Self {
222 - source_text,
223 - line_offsets,
224 - utf16_offsets,
225 - }
226 - }
227 -
228 - fn make_base_node(&self, span: Span) -> BaseNode {
229 - BaseNode {
230 - node_type: None,
231 - start: Some(span.lo.0),
232 - end: Some(span.hi.0),
233 - loc: Some(self.source_location(span)),
234 - range: None,
235 - extra: None,
236 - node_id: Some(span.lo.0),
237 - leading_comments: None,
238 - inner_comments: None,
239 - trailing_comments: None,
240 - }
241 - }
242 -
243 - /// `BytePos` is 1-based; emit 0-based UTF-16 `loc` to match Babel.
244 - /// (`BaseNode.start`/`end` stays 1-based: `convert_scope` keys on it.)
245 - fn position(&self, offset: u32) -> Position {
246 - let zero_based = offset.saturating_sub(1);
247 - let line_idx = match self.line_offsets.binary_search(&zero_based) {
248 - Ok(idx) => idx,
249 - Err(idx) => idx.saturating_sub(1),
250 - };
251 - let line_start = self.line_offsets[line_idx];
252 - // Synthetic spans can point past EOF; clamp to the sentinel.
253 - let byte_idx = (zero_based as usize).min(self.utf16_offsets.len() - 1);
254 - let utf16_offset = self.utf16_offsets[byte_idx];
255 - let line_start_utf16 = self.utf16_offsets[line_start as usize];
256 - Position {
257 - line: (line_idx as u32) + 1,
258 - column: utf16_offset - line_start_utf16,
259 - index: Some(utf16_offset),
260 - }
261 - }
262 -
263 - fn source_location(&self, span: Span) -> SourceLocation {
264 - SourceLocation {
265 - start: self.position(span.lo.0),
266 - end: self.position(span.hi.0),
267 - filename: None,
268 - identifier_name: None,
269 - }
270 - }
271 -
272 - fn convert_module_item(&self, item: &swc::ModuleItem) -> Statement {
273 - match item {
274 - swc::ModuleItem::Stmt(stmt) => self.convert_statement(stmt),
275 - swc::ModuleItem::ModuleDecl(decl) => self.convert_module_decl(decl),
276 - }
277 - }
278 -
279 - fn convert_module_decl(&self, decl: &swc::ModuleDecl) -> Statement {
280 - match decl {
281 - swc::ModuleDecl::Import(d) => {
282 - Statement::ImportDeclaration(self.convert_import_declaration(d))
283 - }
284 - swc::ModuleDecl::ExportDecl(d) => {
285 - Statement::ExportNamedDeclaration(self.convert_export_decl(d))
286 - }
287 - swc::ModuleDecl::ExportNamed(d) => {
288 - Statement::ExportNamedDeclaration(self.convert_export_named(d))
289 - }
290 - swc::ModuleDecl::ExportDefaultDecl(d) => {
291 - Statement::ExportDefaultDeclaration(self.convert_export_default_decl(d))
292 - }
293 - swc::ModuleDecl::ExportDefaultExpr(d) => {
294 - Statement::ExportDefaultDeclaration(self.convert_export_default_expr(d))
295 - }
296 - swc::ModuleDecl::ExportAll(d) => {
297 - Statement::ExportAllDeclaration(self.convert_export_all(d))
298 - }
299 - // These TS module-interop statements are not modeled by the
300 - // typed AST. Carry them as raw Babel-compatible
301 - // `Statement::Unknown` nodes (field names and nesting match
302 - // `@babel/parser`, not byte-exact details like `extra`) so they
303 - // survive the pipeline the same way Babel/NAPI input does.
304 - swc::ModuleDecl::TsImportEquals(d) => self.convert_ts_import_equals(d),
305 - swc::ModuleDecl::TsExportAssignment(d) => self.convert_ts_export_assignment(d),
306 - swc::ModuleDecl::TsNamespaceExport(d) => self.convert_ts_namespace_export(d),
307 - }
308 - }
309 -
310 - // ===== TS module interop (raw unknown statements) =====
311 -
312 - /// Build the base of a raw Babel-shaped JSON node: the `type` tag plus
313 - /// the same `start`/`end`/`loc` fields the converter emits on typed
314 - /// nodes via [`Self::make_base_node`].
315 - fn raw_node_json(&self, node_type: &str, span: Span) -> serde_json::Value {
316 - let mut base = self.make_base_node(span);
317 - base.node_type = Some(node_type.to_string());
318 - serde_json::to_value(base).expect("BaseNode serializes to JSON")
319 - }
320 -
321 - fn identifier_to_json(&self, id: &swc::Ident) -> serde_json::Value {
322 - serde_json::to_value(Expression::Identifier(self.convert_ident_to_identifier(id)))
323 - .expect("Identifier serializes to JSON")
324 - }
325 -
326 - fn ident_name_to_json(&self, id: &swc::IdentName) -> serde_json::Value {
327 - serde_json::to_value(Expression::Identifier(Identifier {
328 - base: self.make_base_node(id.span),
329 - name: id.sym.to_string(),
330 - type_annotation: None,
331 - optional: None,
332 - decorators: None,
333 - }))
334 - .expect("Identifier serializes to JSON")
335 - }
336 -
337 - /// Serialize a TS entity name (`a` or `a.b.c`) as the Babel
338 - /// `Identifier` / `TSQualifiedName` shape.
339 - fn ts_entity_name_to_json(&self, name: &swc::TsEntityName) -> serde_json::Value {
340 - match name {
341 - swc::TsEntityName::Ident(id) => self.identifier_to_json(id),
342 - swc::TsEntityName::TsQualifiedName(q) => {
343 - let mut raw = self.raw_node_json("TSQualifiedName", q.span);
344 - raw["left"] = self.ts_entity_name_to_json(&q.left);
345 - raw["right"] = self.ident_name_to_json(&q.right);
346 - raw
347 - }
348 - }
349 - }
350 -
351 - /// `import x = require("mod")` / `import a = b.c` →
352 - /// `TSImportEqualsDeclaration`.
353 - fn convert_ts_import_equals(&self, d: &swc::TsImportEqualsDecl) -> Statement {
354 - let mut raw = self.raw_node_json("TSImportEqualsDeclaration", d.span);
355 - raw["importKind"] = serde_json::json!(if d.is_type_only { "type" } else { "value" });
356 - raw["isExport"] = serde_json::json!(d.is_export);
357 - raw["id"] = self.identifier_to_json(&d.id);
358 - raw["moduleReference"] = match &d.module_ref {
359 - swc::TsModuleRef::TsExternalModuleRef(r) => {
360 - let mut module_ref = self.raw_node_json("TSExternalModuleReference", r.span);
361 - module_ref["expression"] =
362 - serde_json::to_value(Expression::StringLiteral(StringLiteral {
363 - base: self.make_base_node(r.expr.span),
364 - value: wtf8_to_string(&r.expr.value),
365 - }))
366 - .expect("StringLiteral serializes to JSON");
367 - module_ref
368 - }
369 - swc::TsModuleRef::TsEntityName(name) => self.ts_entity_name_to_json(name),
370 - };
371 - unknown_statement(raw)
372 - }
373 -
374 - /// `export = x` → `TSExportAssignment`. The expression goes through the
375 - /// normal forward conversion; the typed AST serializes to the Babel
376 - /// shape by construction.
377 - fn convert_ts_export_assignment(&self, d: &swc::TsExportAssignment) -> Statement {
378 - let mut raw = self.raw_node_json("TSExportAssignment", d.span);
379 - raw["expression"] = serde_json::to_value(self.convert_expression(&d.expr))
380 - .expect("Expression serializes to JSON");
381 - unknown_statement(raw)
382 - }
383 -
384 - /// `export as namespace X` → `TSNamespaceExportDeclaration`.
385 - fn convert_ts_namespace_export(&self, d: &swc::TsNamespaceExportDecl) -> Statement {
386 - let mut raw = self.raw_node_json("TSNamespaceExportDeclaration", d.span);
387 - raw["id"] = self.identifier_to_json(&d.id);
388 - unknown_statement(raw)
389 - }
390 -
391 - // ===== Statements =====
392 -
393 - fn convert_statement(&self, stmt: &swc::Stmt) -> Statement {
394 - match stmt {
395 - swc::Stmt::Block(s) => Statement::BlockStatement(self.convert_block_statement(s)),
396 - swc::Stmt::Break(s) => Statement::BreakStatement(BreakStatement {
397 - base: self.make_base_node(s.span),
398 - label: s
399 - .label
400 - .as_ref()
401 - .map(|l| self.convert_ident_to_identifier(l)),
402 - }),
403 - swc::Stmt::Continue(s) => Statement::ContinueStatement(ContinueStatement {
404 - base: self.make_base_node(s.span),
405 - label: s
406 - .label
407 - .as_ref()
408 - .map(|l| self.convert_ident_to_identifier(l)),
409 - }),
410 - swc::Stmt::Debugger(s) => Statement::DebuggerStatement(DebuggerStatement {
411 - base: self.make_base_node(s.span),
412 - }),
413 - swc::Stmt::DoWhile(s) => Statement::DoWhileStatement(DoWhileStatement {
414 - base: self.make_base_node(s.span),
415 - test: Box::new(self.convert_expression(&s.test)),
416 - body: Box::new(self.convert_statement(&s.body)),
417 - }),
418 - swc::Stmt::Empty(s) => Statement::EmptyStatement(EmptyStatement {
419 - base: self.make_base_node(s.span),
420 - }),
421 - swc::Stmt::Expr(s) => Statement::ExpressionStatement(ExpressionStatement {
422 - base: self.make_base_node(s.span),
423 - expression: Box::new(self.convert_expression(&s.expr)),
424 - }),
425 - swc::Stmt::ForIn(s) => Statement::ForInStatement(ForInStatement {
426 - base: self.make_base_node(s.span),
427 - left: Box::new(self.convert_for_head(&s.left)),
428 - right: Box::new(self.convert_expression(&s.right)),
429 - body: Box::new(self.convert_statement(&s.body)),
430 - }),
431 - swc::Stmt::ForOf(s) => Statement::ForOfStatement(ForOfStatement {
432 - base: self.make_base_node(s.span),
433 - left: Box::new(self.convert_for_head(&s.left)),
434 - right: Box::new(self.convert_expression(&s.right)),
435 - body: Box::new(self.convert_statement(&s.body)),
436 - is_await: s.is_await,
437 - }),
438 - swc::Stmt::For(s) => Statement::ForStatement(ForStatement {
439 - base: self.make_base_node(s.span),
440 - init: s
441 - .init
442 - .as_ref()
443 - .map(|i| Box::new(self.convert_var_decl_or_expr_to_for_init(i))),
444 - test: s
445 - .test
446 - .as_ref()
447 - .map(|t| Box::new(self.convert_expression(t))),
448 - update: s
449 - .update
450 - .as_ref()
451 - .map(|u| Box::new(self.convert_expression(u))),
452 - body: Box::new(self.convert_statement(&s.body)),
453 - }),
454 - swc::Stmt::If(s) => Statement::IfStatement(IfStatement {
455 - base: self.make_base_node(s.span),
456 - test: Box::new(self.convert_expression(&s.test)),
457 - consequent: Box::new(self.convert_statement(&s.cons)),
458 - alternate: s.alt.as_ref().map(|a| Box::new(self.convert_statement(a))),
459 - }),
460 - swc::Stmt::Labeled(s) => Statement::LabeledStatement(LabeledStatement {
461 - base: self.make_base_node(s.span),
462 - label: self.convert_ident_to_identifier(&s.label),
463 - body: Box::new(self.convert_statement(&s.body)),
464 - }),
465 - swc::Stmt::Return(s) => Statement::ReturnStatement(ReturnStatement {
466 - base: self.make_base_node(s.span),
467 - argument: s.arg.as_ref().map(|a| Box::new(self.convert_expression(a))),
468 - }),
469 - swc::Stmt::Switch(s) => Statement::SwitchStatement(SwitchStatement {
470 - base: self.make_base_node(s.span),
471 - discriminant: Box::new(self.convert_expression(&s.discriminant)),
472 - cases: s
473 - .cases
474 - .iter()
475 - .map(|c| SwitchCase {
476 - base: self.make_base_node(c.span),
477 - test: c
478 - .test
479 - .as_ref()
480 - .map(|t| Box::new(self.convert_expression(t))),
481 - consequent: c.cons.iter().map(|s| self.convert_statement(s)).collect(),
482 - })
483 - .collect(),
484 - }),
485 - swc::Stmt::Throw(s) => Statement::ThrowStatement(ThrowStatement {
486 - base: self.make_base_node(s.span),
487 - argument: Box::new(self.convert_expression(&s.arg)),
488 - }),
489 - swc::Stmt::Try(s) => Statement::TryStatement(TryStatement {
490 - base: self.make_base_node(s.span),
491 - block: self.convert_block_statement(&s.block),
492 - handler: s.handler.as_ref().map(|h| self.convert_catch_clause(h)),
493 - finalizer: s
494 - .finalizer
495 - .as_ref()
496 - .map(|f| self.convert_block_statement(f)),
497 - }),
498 - swc::Stmt::While(s) => Statement::WhileStatement(WhileStatement {
499 - base: self.make_base_node(s.span),
500 - test: Box::new(self.convert_expression(&s.test)),
501 - body: Box::new(self.convert_statement(&s.body)),
502 - }),
503 - swc::Stmt::With(s) => Statement::WithStatement(WithStatement {
504 - base: self.make_base_node(s.span),
505 - object: Box::new(self.convert_expression(&s.obj)),
506 - body: Box::new(self.convert_statement(&s.body)),
507 - }),
508 - swc::Stmt::Decl(d) => self.convert_decl_to_statement(d),
509 - }
510 - }
511 -
512 - fn convert_decl_to_statement(&self, decl: &swc::Decl) -> Statement {
513 - match decl {
514 - swc::Decl::Var(v) => {
515 - Statement::VariableDeclaration(self.convert_variable_declaration(v))
516 - }
517 - swc::Decl::Fn(f) => Statement::FunctionDeclaration(self.convert_fn_decl(f)),
518 - swc::Decl::Class(c) => Statement::ClassDeclaration(self.convert_class_decl(c)),
519 - swc::Decl::TsTypeAlias(d) => {
520 - Statement::TSTypeAliasDeclaration(self.convert_ts_type_alias(d))
521 - }
522 - swc::Decl::TsInterface(d) => {
523 - Statement::TSInterfaceDeclaration(self.convert_ts_interface(d))
524 - }
525 - swc::Decl::TsEnum(d) => Statement::TSEnumDeclaration(self.convert_ts_enum(d)),
526 - swc::Decl::TsModule(d) => Statement::TSModuleDeclaration(self.convert_ts_module(d)),
527 - swc::Decl::Using(u) => Statement::VariableDeclaration(self.convert_using_decl(u)),
528 - }
529 - }
530 -
531 - fn convert_block_statement(&self, block: &swc::BlockStmt) -> BlockStatement {
532 - let mut body: Vec<Statement> = Vec::new();
533 - let mut directives: Vec<Directive> = Vec::new();
534 - let mut past_directives = false;
535 -
536 - for stmt in &block.stmts {
537 - if !past_directives {
538 - if let Some(dir) = self.try_extract_block_directive(stmt) {
539 - directives.push(dir);
540 - continue;
541 - }
542 - past_directives = true;
543 - }
544 - body.push(self.convert_statement(stmt));
545 - }
546 -
547 - BlockStatement {
548 - base: self.make_base_node(block.span),
549 - body,
550 - directives,
551 - }
552 - }
553 -
554 - /// Try to extract a directive from a statement in a block body.
555 - /// Directives are expression statements whose expression is a string literal.
556 - fn try_extract_block_directive(&self, stmt: &swc::Stmt) -> Option<Directive> {
557 - if let swc::Stmt::Expr(expr_stmt) = stmt {
558 - if let swc::Expr::Lit(swc::Lit::Str(s)) = &*expr_stmt.expr {
559 - return Some(Directive {
560 - base: self.make_base_node(expr_stmt.span),
561 - value: DirectiveLiteral {
562 - base: self.make_base_node(s.span),
563 - value: wtf8_to_string(&s.value),
564 - },
565 - });
566 - }
567 - }
568 - None
569 - }
570 -
571 - fn convert_catch_clause(&self, clause: &swc::CatchClause) -> CatchClause {
572 - CatchClause {
573 - base: self.make_base_node(clause.span),
574 - param: clause.param.as_ref().map(|p| self.convert_pat(p)),
575 - body: self.convert_block_statement(&clause.body),
576 - }
577 - }
578 -
579 - fn convert_var_decl_or_expr_to_for_init(&self, init: &swc::VarDeclOrExpr) -> ForInit {
580 - match init {
581 - swc::VarDeclOrExpr::VarDecl(v) => {
582 - ForInit::VariableDeclaration(self.convert_variable_declaration(v))
583 - }
584 - swc::VarDeclOrExpr::Expr(e) => {
585 - ForInit::Expression(Box::new(self.convert_expression(e)))
586 - }
587 - }
588 - }
589 -
590 - fn convert_for_head(&self, head: &swc::ForHead) -> ForInOfLeft {
591 - match head {
592 - swc::ForHead::VarDecl(v) => {
593 - ForInOfLeft::VariableDeclaration(self.convert_variable_declaration(v))
594 - }
595 - swc::ForHead::Pat(p) => ForInOfLeft::Pattern(Box::new(self.convert_pat(p))),
596 - swc::ForHead::UsingDecl(u) => {
597 - ForInOfLeft::VariableDeclaration(self.convert_using_decl(u))
598 - }
599 - }
600 - }
601 -
602 - fn convert_variable_declaration(&self, decl: &swc::VarDecl) -> VariableDeclaration {
603 - VariableDeclaration {
604 - base: self.make_base_node(decl.span),
605 - declarations: decl
606 - .decls
607 - .iter()
608 - .map(|d| self.convert_variable_declarator(d))
609 - .collect(),
610 - kind: match decl.kind {
611 - swc::VarDeclKind::Var => VariableDeclarationKind::Var,
612 - swc::VarDeclKind::Let => VariableDeclarationKind::Let,
613 - swc::VarDeclKind::Const => VariableDeclarationKind::Const,
614 - },
615 - declare: if decl.declare { Some(true) } else { None },
616 - }
617 - }
618 -
619 - fn convert_using_decl(&self, decl: &swc::UsingDecl) -> VariableDeclaration {
620 - VariableDeclaration {
621 - base: self.make_base_node(decl.span),
622 - declarations: decl
623 - .decls
624 - .iter()
625 - .map(|d| self.convert_variable_declarator(d))
626 - .collect(),
627 - kind: VariableDeclarationKind::Using,
628 - declare: None,
629 - }
630 - }
631 -
632 - fn convert_variable_declarator(&self, d: &swc::VarDeclarator) -> VariableDeclarator {
633 - VariableDeclarator {
634 - base: self.make_base_node(d.span),
635 - id: self.convert_pat(&d.name),
636 - init: d
637 - .init
638 - .as_ref()
639 - .map(|e| Box::new(self.convert_expression(e))),
640 - definite: if d.definite { Some(true) } else { None },
641 - }
642 - }
643 -
644 - // ===== Expressions =====
645 -
646 - fn convert_expression(&self, expr: &swc::Expr) -> Expression {
647 - match expr {
648 - swc::Expr::Lit(lit) => self.convert_lit(lit),
649 - swc::Expr::Ident(id) => Expression::Identifier(self.convert_ident_to_identifier(id)),
650 - swc::Expr::This(t) => Expression::ThisExpression(ThisExpression {
651 - base: self.make_base_node(t.span),
652 - }),
653 - swc::Expr::Array(arr) => {
654 - Expression::ArrayExpression(self.convert_array_expression(arr))
655 - }
656 - swc::Expr::Object(obj) => {
657 - Expression::ObjectExpression(self.convert_object_expression(obj))
658 - }
659 - swc::Expr::Fn(f) => Expression::FunctionExpression(self.convert_fn_expr(f)),
660 - swc::Expr::Unary(un) => Expression::UnaryExpression(UnaryExpression {
661 - base: self.make_base_node(un.span),
662 - operator: self.convert_unary_operator(un.op),
663 - prefix: true,
664 - argument: Box::new(self.convert_expression(&un.arg)),
665 - }),
666 - swc::Expr::Update(up) => Expression::UpdateExpression(UpdateExpression {
667 - base: self.make_base_node(up.span),
668 - operator: self.convert_update_operator(up.op),
669 - argument: Box::new(self.convert_expression(&up.arg)),
670 - prefix: up.prefix,
671 - }),
672 - swc::Expr::Bin(bin) => {
673 - if let Some(log_op) = self.try_convert_logical_operator(bin.op) {
674 - Expression::LogicalExpression(LogicalExpression {
675 - base: self.make_base_node(bin.span),
676 - operator: log_op,
677 - left: Box::new(self.convert_expression(&bin.left)),
678 - right: Box::new(self.convert_expression(&bin.right)),
679 - })
680 - } else {
681 - Expression::BinaryExpression(BinaryExpression {
682 - base: self.make_base_node(bin.span),
683 - operator: self.convert_binary_operator(bin.op),
684 - left: Box::new(self.convert_expression(&bin.left)),
685 - right: Box::new(self.convert_expression(&bin.right)),
686 - })
687 - }
688 - }
689 - swc::Expr::Assign(a) => {
690 - Expression::AssignmentExpression(self.convert_assignment_expression(a))
691 - }
692 - swc::Expr::Member(m) => Expression::MemberExpression(self.convert_member_expression(m)),
693 - swc::Expr::SuperProp(sp) => {
694 - let (property, computed) = self.convert_super_prop(&sp.prop);
695 - Expression::MemberExpression(MemberExpression {
696 - base: self.make_base_node(sp.span),
697 - object: Box::new(Expression::Super(Super {
698 - base: self.make_base_node(sp.obj.span),
699 - })),
700 - property: Box::new(property),
701 - computed,
702 - })
703 - }
704 - swc::Expr::Cond(c) => Expression::ConditionalExpression(ConditionalExpression {
705 - base: self.make_base_node(c.span),
706 - test: Box::new(self.convert_expression(&c.test)),
707 - consequent: Box::new(self.convert_expression(&c.cons)),
708 - alternate: Box::new(self.convert_expression(&c.alt)),
709 - }),
710 - swc::Expr::Call(call) => Expression::CallExpression(self.convert_call_expression(call)),
711 - swc::Expr::New(n) => Expression::NewExpression(NewExpression {
712 - base: self.make_base_node(n.span),
713 - callee: Box::new(self.convert_expression(&n.callee)),
714 - arguments: n.args.as_ref().map_or_else(Vec::new, |args| {
715 - args.iter()
716 - .map(|a| self.convert_expr_or_spread(a))
717 - .collect()
718 - }),
719 - type_parameters: None,
720 - type_arguments: None,
721 - }),
722 - swc::Expr::Seq(seq) => Expression::SequenceExpression(SequenceExpression {
723 - base: self.make_base_node(seq.span),
724 - expressions: seq
725 - .exprs
726 - .iter()
727 - .map(|e| self.convert_expression(e))
728 - .collect(),
729 - }),
730 - swc::Expr::Arrow(arrow) => {
731 - Expression::ArrowFunctionExpression(self.convert_arrow_function(arrow))
732 - }
733 - swc::Expr::Class(class) => {
734 - Expression::ClassExpression(self.convert_class_expression(class))
735 - }
736 - swc::Expr::Yield(y) => Expression::YieldExpression(YieldExpression {
737 - base: self.make_base_node(y.span),
738 - argument: y.arg.as_ref().map(|a| Box::new(self.convert_expression(a))),
739 - delegate: y.delegate,
740 - }),
741 - swc::Expr::Await(a) => Expression::AwaitExpression(AwaitExpression {
742 - base: self.make_base_node(a.span),
743 - argument: Box::new(self.convert_expression(&a.arg)),
744 - }),
745 - swc::Expr::MetaProp(mp) => {
746 - let (meta_name, prop_name) = match mp.kind {
747 - swc::MetaPropKind::NewTarget => ("new", "target"),
748 - swc::MetaPropKind::ImportMeta => ("import", "meta"),
749 - };
750 - Expression::MetaProperty(MetaProperty {
751 - base: self.make_base_node(mp.span),
752 - meta: Identifier {
753 - base: self.make_base_node(mp.span),
754 - name: meta_name.to_string(),
755 - type_annotation: None,
756 - optional: None,
757 - decorators: None,
758 - },
759 - property: Identifier {
760 - base: self.make_base_node(mp.span),
761 - name: prop_name.to_string(),
762 - type_annotation: None,
763 - optional: None,
764 - decorators: None,
765 - },
766 - })
767 - }
768 - swc::Expr::Tpl(tpl) => Expression::TemplateLiteral(self.convert_template_literal(tpl)),
769 - swc::Expr::TaggedTpl(tag) => {
770 - Expression::TaggedTemplateExpression(TaggedTemplateExpression {
771 - base: self.make_base_node(tag.span),
772 - tag: Box::new(self.convert_expression(&tag.tag)),
773 - quasi: self.convert_template_literal(&tag.tpl),
774 - type_parameters: None,
775 - })
776 - }
777 - swc::Expr::Paren(p) => Expression::ParenthesizedExpression(ParenthesizedExpression {
778 - base: self.make_base_node(p.span),
779 - expression: Box::new(self.convert_expression(&p.expr)),
780 - }),
781 - swc::Expr::OptChain(chain) => self.convert_opt_chain_expression(chain),
782 - swc::Expr::PrivateName(p) => Expression::PrivateName(PrivateName {
783 - base: self.make_base_node(p.span),
784 - id: Identifier {
785 - base: self.make_base_node(p.span),
786 - name: p.name.to_string(),
787 - type_annotation: None,
788 - optional: None,
789 - decorators: None,
790 - },
791 - }),
792 - swc::Expr::JSXElement(el) => {
793 - Expression::JSXElement(Box::new(self.convert_jsx_element(el)))
794 - }
795 - swc::Expr::JSXFragment(frag) => {
796 - Expression::JSXFragment(self.convert_jsx_fragment(frag))
797 - }
798 - swc::Expr::JSXEmpty(e) => Expression::Identifier(Identifier {
799 - base: self.make_base_node(e.span),
800 - name: "undefined".to_string(),
801 - type_annotation: None,
802 - optional: None,
803 - decorators: None,
804 - }),
805 - swc::Expr::JSXMember(m) => Expression::Identifier(Identifier {
806 - base: self.make_base_node(m.prop.span),
807 - name: m.prop.sym.to_string(),
808 - type_annotation: None,
809 - optional: None,
810 - decorators: None,
811 - }),
812 - swc::Expr::JSXNamespacedName(n) => Expression::Identifier(Identifier {
813 - base: self.make_base_node(n.name.span),
814 - name: format!("{}:{}", n.ns.sym, n.name.sym),
815 - type_annotation: None,
816 - optional: None,
817 - decorators: None,
818 - }),
819 - swc::Expr::TsAs(e) => Expression::TSAsExpression(TSAsExpression {
820 - base: self.make_base_node(e.span),
821 - expression: Box::new(self.convert_expression(&e.expr)),
822 - type_annotation: RawNode::from_value(
823 - &self.convert_ts_type_to_json(&e.type_ann)
824 - .unwrap_or(serde_json::Value::Null),
825 - ),
826 - }),
827 - swc::Expr::TsSatisfies(e) => Expression::TSSatisfiesExpression(TSSatisfiesExpression {
828 - base: self.make_base_node(e.span),
829 - expression: Box::new(self.convert_expression(&e.expr)),
830 - type_annotation: RawNode::from_value(
831 - &self.convert_ts_type_to_json(&e.type_ann)
832 - .unwrap_or(serde_json::Value::Null),
833 - ),
834 - }),
835 - swc::Expr::TsTypeAssertion(e) => Expression::TSTypeAssertion(TSTypeAssertion {
836 - base: self.make_base_node(e.span),
837 - expression: Box::new(self.convert_expression(&e.expr)),
838 - type_annotation: RawNode::from_value(
839 - &self.convert_ts_type_to_json(&e.type_ann)
840 - .unwrap_or(serde_json::Value::Null),
841 - ),
842 - }),
843 - swc::Expr::TsNonNull(e) => Expression::TSNonNullExpression(TSNonNullExpression {
844 - base: self.make_base_node(e.span),
845 - expression: Box::new(self.convert_expression(&e.expr)),
846 - }),
847 - swc::Expr::TsInstantiation(e) => {
848 - Expression::TSInstantiationExpression(TSInstantiationExpression {
849 - base: self.make_base_node(e.span),
850 - expression: Box::new(self.convert_expression(&e.expr)),
851 - type_parameters: RawNode::null(),
852 - })
853 - }
854 - swc::Expr::TsConstAssertion(e) => {
855 - // "as const" → TSAsExpression with typeAnnotation: TSTypeReference { typeName: Identifier { name: "const" } }
856 - // This matches Babel's AST representation of `as const`.
857 - let type_ann = serde_json::json!({
858 - "type": "TSTypeReference",
859 - "typeName": {
860 - "type": "Identifier",
861 - "name": "const"
862 - }
863 - });
864 - Expression::TSAsExpression(TSAsExpression {
865 - base: self.make_base_node(e.span),
866 - expression: Box::new(self.convert_expression(&e.expr)),
867 - type_annotation: RawNode::from_value(&type_ann),
868 - })
869 - }
870 - swc::Expr::Invalid(i) => Expression::Identifier(Identifier {
871 - base: self.make_base_node(i.span),
872 - name: "__invalid__".to_string(),
873 - type_annotation: None,
874 - optional: None,
875 - decorators: None,
876 - }),
877 - }
878 - }
879 -
880 - fn convert_lit(&self, lit: &swc::Lit) -> Expression {
881 - match lit {
882 - swc::Lit::Str(s) => Expression::StringLiteral(StringLiteral {
883 - base: self.make_base_node(s.span),
884 - value: wtf8_to_string(&s.value),
885 - }),
886 - swc::Lit::Bool(b) => Expression::BooleanLiteral(BooleanLiteral {
887 - base: self.make_base_node(b.span),
888 - value: b.value,
889 - }),
890 - swc::Lit::Null(n) => Expression::NullLiteral(NullLiteral {
891 - base: self.make_base_node(n.span),
892 - }),
893 - swc::Lit::Num(n) => Expression::NumericLiteral(NumericLiteral {
894 - base: self.make_base_node(n.span),
895 - value: n.value,
896 - extra: None,
897 - }),
898 - swc::Lit::BigInt(b) => Expression::BigIntLiteral(BigIntLiteral {
899 - base: self.make_base_node(b.span),
900 - value: b.value.to_string(),
901 - }),
902 - swc::Lit::Regex(r) => Expression::RegExpLiteral(RegExpLiteral {
903 - base: self.make_base_node(r.span),
904 - pattern: r.exp.to_string(),
905 - flags: r.flags.to_string(),
906 - }),
907 - swc::Lit::JSXText(t) => Expression::StringLiteral(StringLiteral {
908 - base: self.make_base_node(t.span),
909 - value: t.value.to_string(),
910 - }),
911 - }
912 - }
913 -
914 - // ===== Optional chaining =====
915 -
916 - fn convert_opt_chain_expression(&self, chain: &swc::OptChainExpr) -> Expression {
917 - match &*chain.base {
918 - swc::OptChainBase::Member(m) => {
919 - let (property, computed) = self.convert_member_prop(&m.prop);
920 - Expression::OptionalMemberExpression(OptionalMemberExpression {
921 - base: self.make_base_node(chain.span),
922 - object: Box::new(self.convert_opt_chain_callee(&m.obj)),
923 - property: Box::new(property),
924 - computed,
925 - optional: chain.optional,
926 - })
927 - }
928 - swc::OptChainBase::Call(call) => {
929 - Expression::OptionalCallExpression(OptionalCallExpression {
930 - base: self.make_base_node(chain.span),
931 - callee: Box::new(self.convert_opt_chain_callee(&call.callee)),
932 - arguments: call
933 - .args
934 - .iter()
935 - .map(|a| self.convert_expr_or_spread(a))
936 - .collect(),
937 - optional: chain.optional,
938 - type_parameters: None,
939 - type_arguments: None,
940 - })
941 - }
942 - }
943 - }
944 -
945 - fn convert_opt_chain_callee(&self, expr: &swc::Expr) -> Expression {
946 - if let swc::Expr::OptChain(chain) = expr {
947 - return self.convert_opt_chain_expression(chain);
948 - }
949 - self.convert_expression(expr)
950 - }
951 -
952 - // ===== Member expression =====
953 -
954 - fn convert_member_expression(&self, m: &swc::MemberExpr) -> MemberExpression {
955 - let (property, computed) = self.convert_member_prop(&m.prop);
956 - MemberExpression {
957 - base: self.make_base_node(m.span),
958 - object: Box::new(self.convert_expression(&m.obj)),
959 - property: Box::new(property),
960 - computed,
961 - }
962 - }
963 -
964 - fn convert_member_prop(&self, prop: &swc::MemberProp) -> (Expression, bool) {
965 - match prop {
966 - swc::MemberProp::Ident(id) => (
967 - Expression::Identifier(Identifier {
968 - base: self.make_base_node(id.span),
969 - name: id.sym.to_string(),
970 - type_annotation: None,
971 - optional: None,
972 - decorators: None,
973 - }),
974 - false,
975 - ),
976 - swc::MemberProp::Computed(c) => (self.convert_expression(&c.expr), true),
977 - swc::MemberProp::PrivateName(p) => (
978 - Expression::PrivateName(PrivateName {
979 - base: self.make_base_node(p.span),
980 - id: Identifier {
981 - base: self.make_base_node(p.span),
982 - name: p.name.to_string(),
983 - type_annotation: None,
984 - optional: None,
985 - decorators: None,
986 - },
987 - }),
988 - false,
989 - ),
990 - }
991 - }
992 -
993 - fn convert_super_prop(&self, prop: &swc::SuperProp) -> (Expression, bool) {
994 - match prop {
995 - swc::SuperProp::Ident(id) => (
996 - Expression::Identifier(Identifier {
997 - base: self.make_base_node(id.span),
998 - name: id.sym.to_string(),
999 - type_annotation: None,
1000 - optional: None,
1001 - decorators: None,
1002 - }),
1003 - false,
1004 - ),
1005 - swc::SuperProp::Computed(c) => (self.convert_expression(&c.expr), true),
1006 - }
1007 - }
1008 -
1009 - // ===== Call expression =====
1010 -
1011 - fn convert_call_expression(&self, call: &swc::CallExpr) -> CallExpression {
1012 - CallExpression {
1013 - base: self.make_base_node(call.span),
1014 - callee: Box::new(self.convert_callee(&call.callee)),
1015 - arguments: call
1016 - .args
1017 - .iter()
1018 - .map(|a| self.convert_expr_or_spread(a))
1019 - .collect(),
1020 - type_parameters: None,
1021 - type_arguments: None,
1022 - optional: None,
1023 - }
1024 - }
1025 -
1026 - fn convert_callee(&self, callee: &swc::Callee) -> Expression {
1027 - match callee {
1028 - swc::Callee::Expr(e) => self.convert_expression(e),
1029 - swc::Callee::Super(s) => Expression::Super(Super {
1030 - base: self.make_base_node(s.span),
1031 - }),
1032 - swc::Callee::Import(i) => Expression::Import(Import {
1033 - base: self.make_base_node(i.span),
1034 - }),
1035 - }
1036 - }
1037 -
1038 - fn convert_expr_or_spread(&self, arg: &swc::ExprOrSpread) -> Expression {
1039 - if let Some(spread_span) = arg.spread {
1040 - Expression::SpreadElement(SpreadElement {
1041 - base: self.make_base_node(Span::new(spread_span.lo, arg.expr.span().hi)),
1042 - argument: Box::new(self.convert_expression(&arg.expr)),
1043 - })
1044 - } else {
1045 - self.convert_expression(&arg.expr)
1046 - }
1047 - }
1048 -
1049 - // ===== Function helpers =====
1050 -
1051 - fn convert_fn_decl(&self, func: &swc::FnDecl) -> FunctionDeclaration {
1052 - let f = &func.function;
1053 - let body = f
1054 - .body
1055 - .as_ref()
1056 - .map(|b| self.convert_block_statement(b))
1057 - .unwrap_or_else(|| BlockStatement {
1058 - base: self.make_base_node(f.span),
1059 - body: vec![],
1060 - directives: vec![],
1061 - });
1062 - FunctionDeclaration {
1063 - base: self.make_base_node(f.span),
1064 - id: Some(self.convert_ident_to_identifier(&func.ident)),
1065 - params: self.convert_params(&f.params),
1066 - body,
1067 - generator: f.is_generator,
1068 - is_async: f.is_async,
1069 - declare: if func.declare { Some(true) } else { None },
1070 - return_type: f
1071 - .return_type
1072 - .as_ref()
1073 - .map(|_| RawNode::null()),
1074 - type_parameters: f
1075 - .type_params
1076 - .as_ref()
1077 - .map(|_| RawNode::null()),
1078 - predicate: None,
1079 - component_declaration: false,
1080 - hook_declaration: false,
1081 - }
1082 - }
1083 -
1084 - fn convert_fn_expr(&self, func: &swc::FnExpr) -> FunctionExpression {
1085 - let f = &func.function;
1086 - let body = f
1087 - .body
1088 - .as_ref()
1089 - .map(|b| self.convert_block_statement(b))
1090 - .unwrap_or_else(|| BlockStatement {
1091 - base: self.make_base_node(f.span),
1092 - body: vec![],
1093 - directives: vec![],
1094 - });
1095 - FunctionExpression {
1096 - base: self.make_base_node(f.span),
1097 - id: func
1098 - .ident
1099 - .as_ref()
1100 - .map(|id| self.convert_ident_to_identifier(id)),
1101 - params: self.convert_params(&f.params),
1102 - body,
1103 - generator: f.is_generator,
1104 - is_async: f.is_async,
1105 - return_type: f
1106 - .return_type
1107 - .as_ref()
1108 - .map(|_| RawNode::null()),
1109 - type_parameters: f
1110 - .type_params
1111 - .as_ref()
1112 - .map(|_| RawNode::null()),
1113 - predicate: None,
1114 - }
1115 - }
1116 -
1117 - fn convert_arrow_function(&self, arrow: &swc::ArrowExpr) -> ArrowFunctionExpression {
1118 - let is_expression = matches!(&*arrow.body, swc::BlockStmtOrExpr::Expr(_));
1119 - let body = match &*arrow.body {
1120 - swc::BlockStmtOrExpr::BlockStmt(block) => {
1121 - ArrowFunctionBody::BlockStatement(self.convert_block_statement(block))
1122 - }
1123 - swc::BlockStmtOrExpr::Expr(expr) => {
1124 - ArrowFunctionBody::Expression(Box::new(self.convert_expression(expr)))
1125 - }
1126 - };
1127 - ArrowFunctionExpression {
1128 - base: self.make_base_node(arrow.span),
1129 - params: arrow.params.iter().map(|p| self.convert_pat(p)).collect(),
1130 - body: Box::new(body),
1131 - id: None,
1132 - generator: arrow.is_generator,
1133 - is_async: arrow.is_async,
1134 - expression: Some(is_expression),
1135 - return_type: arrow
1136 - .return_type
1137 - .as_ref()
1138 - .map(|_| RawNode::null()),
1139 - type_parameters: arrow
1140 - .type_params
1141 - .as_ref()
1142 - .map(|_| RawNode::null()),
1143 - predicate: None,
1144 - }
1145 - }
1146 -
1147 - fn convert_params(&self, params: &[swc::Param]) -> Vec<PatternLike> {
1148 - params.iter().map(|p| self.convert_pat(&p.pat)).collect()
1149 - }
1150 -
1151 - // ===== Patterns =====
1152 -
1153 - fn convert_pat(&self, pat: &swc::Pat) -> PatternLike {
1154 - match pat {
1155 - swc::Pat::Ident(id) => PatternLike::Identifier(self.convert_binding_ident(id)),
1156 - swc::Pat::Array(arr) => PatternLike::ArrayPattern(self.convert_array_pattern(arr)),
1157 - swc::Pat::Object(obj) => PatternLike::ObjectPattern(self.convert_object_pattern(obj)),
1158 - swc::Pat::Assign(a) => PatternLike::AssignmentPattern(AssignmentPattern {
1159 - base: self.make_base_node(a.span),
1160 - left: Box::new(self.convert_pat(&a.left)),
1161 - right: Box::new(self.convert_expression(&a.right)),
1162 - type_annotation: None,
1163 - decorators: None,
1164 - }),
1165 - swc::Pat::Rest(r) => PatternLike::RestElement(RestElement {
1166 - base: self.make_base_node(r.span),
1167 - argument: Box::new(self.convert_pat(&r.arg)),
1168 - type_annotation: None,
1169 - decorators: None,
1170 - }),
1171 - swc::Pat::Expr(e) => self.convert_expression_to_pattern(e),
1172 - swc::Pat::Invalid(i) => PatternLike::Identifier(Identifier {
1173 - base: self.make_base_node(i.span),
1174 - name: "__invalid__".to_string(),
1175 - type_annotation: None,
1176 - optional: None,
1177 - decorators: None,
1178 - }),
1179 - }
1180 - }
1181 -
1182 - fn convert_expression_to_pattern(&self, expr: &swc::Expr) -> PatternLike {
1183 - match expr {
1184 - swc::Expr::Ident(id) => PatternLike::Identifier(self.convert_ident_to_identifier(id)),
1185 - swc::Expr::Member(m) => {
1186 - PatternLike::MemberExpression(self.convert_member_expression(m))
1187 - }
1188 - _ => PatternLike::Identifier(Identifier {
1189 - base: self.make_base_node(expr.span()),
1190 - name: "__unknown_target__".to_string(),
1191 - type_annotation: None,
1192 - optional: None,
1193 - decorators: None,
1194 - }),
1195 - }
1196 - }
1197 -
1198 - fn convert_object_pattern(&self, obj: &swc::ObjectPat) -> ObjectPattern {
1199 - let properties = obj
1200 - .props
1201 - .iter()
1202 - .map(|p| match p {
1203 - swc::ObjectPatProp::KeyValue(kv) => {
1204 - ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
1205 - base: self.make_base_node(kv.span()),
1206 - key: Box::new(self.convert_prop_name(&kv.key)),
1207 - value: Box::new(self.convert_pat(&kv.value)),
1208 - computed: matches!(kv.key, swc::PropName::Computed(_)),
1209 - shorthand: false,
1210 - decorators: None,
1211 - method: None,
1212 - })
1213 - }
1214 - swc::ObjectPatProp::Assign(a) => {
1215 - let id = self.convert_ident_to_identifier(&a.key.id);
1216 - let (value, shorthand) = if let Some(ref init) = a.value {
1217 - (
1218 - Box::new(PatternLike::AssignmentPattern(AssignmentPattern {
1219 - base: self.make_base_node(a.span),
1220 - left: Box::new(PatternLike::Identifier(id.clone())),
1221 - right: Box::new(self.convert_expression(init)),
1222 - type_annotation: None,
1223 - decorators: None,
1224 - })),
1225 - true,
1226 - )
1227 - } else {
1228 - (Box::new(PatternLike::Identifier(id.clone())), true)
1229 - };
1230 - ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
1231 - base: self.make_base_node(a.span),
1232 - key: Box::new(Expression::Identifier(id)),
1233 - value,
1234 - computed: false,
1235 - shorthand,
1236 - decorators: None,
1237 - method: None,
1238 - })
1239 - }
1240 - swc::ObjectPatProp::Rest(r) => ObjectPatternProperty::RestElement(RestElement {
1241 - base: self.make_base_node(r.span),
1242 - argument: Box::new(self.convert_pat(&r.arg)),
1243 - type_annotation: None,
1244 - decorators: None,
1245 - }),
1246 - })
1247 - .collect();
1248 - ObjectPattern {
1249 - base: self.make_base_node(obj.span),
1250 - properties,
1251 - type_annotation: obj
1252 - .type_ann
1253 - .as_ref()
1254 - .map(|_| RawNode::null()),
1255 - decorators: None,
1256 - }
1257 - }
1258 -
1259 - fn convert_array_pattern(&self, arr: &swc::ArrayPat) -> ArrayPattern {
1260 - ArrayPattern {
1261 - base: self.make_base_node(arr.span),
1262 - elements: arr
1263 - .elems
1264 - .iter()
1265 - .map(|e| e.as_ref().map(|p| self.convert_pat(p)))
1266 - .collect(),
1267 - type_annotation: arr
1268 - .type_ann
1269 - .as_ref()
1270 - .map(|_| RawNode::null()),
1271 - decorators: None,
1272 - }
1273 - }
1274 -
1275 - // ===== AssignmentTarget =====
1276 -
1277 - fn convert_assign_target(&self, target: &swc::AssignTarget) -> PatternLike {
1278 - match target {
1279 - swc::AssignTarget::Simple(s) => self.convert_simple_assign_target(s),
1280 - swc::AssignTarget::Pat(p) => self.convert_assign_target_pat(p),
1281 - }
1282 - }
1283 -
1284 - fn convert_simple_assign_target(&self, target: &swc::SimpleAssignTarget) -> PatternLike {
1285 - match target {
1286 - swc::SimpleAssignTarget::Ident(id) => {
1287 - PatternLike::Identifier(self.convert_binding_ident(id))
1288 - }
1289 - swc::SimpleAssignTarget::Member(m) => {
1290 - PatternLike::MemberExpression(self.convert_member_expression(m))
1291 - }
1292 - swc::SimpleAssignTarget::SuperProp(sp) => {
1293 - let (property, computed) = self.convert_super_prop(&sp.prop);
1294 - PatternLike::MemberExpression(MemberExpression {
1295 - base: self.make_base_node(sp.span),
1296 - object: Box::new(Expression::Super(Super {
1297 - base: self.make_base_node(sp.obj.span),
1298 - })),
1299 - property: Box::new(property),
1300 - computed,
1301 - })
1302 - }
1303 - swc::SimpleAssignTarget::Paren(p) => self.convert_expression_to_pattern(&p.expr),
1304 - swc::SimpleAssignTarget::OptChain(o) => PatternLike::Identifier(Identifier {
1305 - base: self.make_base_node(o.span),
1306 - name: "__unknown_target__".to_string(),
1307 - type_annotation: None,
1308 - optional: None,
1309 - decorators: None,
1310 - }),
1311 - swc::SimpleAssignTarget::TsAs(e) => self.convert_expression_to_pattern(&e.expr),
1312 - swc::SimpleAssignTarget::TsSatisfies(e) => self.convert_expression_to_pattern(&e.expr),
1313 - swc::SimpleAssignTarget::TsNonNull(e) => self.convert_expression_to_pattern(&e.expr),
1314 - swc::SimpleAssignTarget::TsTypeAssertion(e) => {
1315 - self.convert_expression_to_pattern(&e.expr)
1316 - }
1317 - swc::SimpleAssignTarget::TsInstantiation(e) => {
1318 - self.convert_expression_to_pattern(&e.expr)
1319 - }
1320 - swc::SimpleAssignTarget::Invalid(i) => PatternLike::Identifier(Identifier {
1321 - base: self.make_base_node(i.span),
1322 - name: "__invalid__".to_string(),
1323 - type_annotation: None,
1324 - optional: None,
1325 - decorators: None,
1326 - }),
1327 - }
1328 - }
1329 -
1330 - fn convert_assign_target_pat(&self, target: &swc::AssignTargetPat) -> PatternLike {
1331 - match target {
1332 - swc::AssignTargetPat::Array(a) => {
1333 - PatternLike::ArrayPattern(self.convert_array_pattern(a))
1334 - }
1335 - swc::AssignTargetPat::Object(o) => {
1336 - PatternLike::ObjectPattern(self.convert_object_pattern(o))
1337 - }
1338 - swc::AssignTargetPat::Invalid(i) => PatternLike::Identifier(Identifier {
1339 - base: self.make_base_node(i.span),
1340 - name: "__invalid__".to_string(),
1341 - type_annotation: None,
1342 - optional: None,
1343 - decorators: None,
1344 - }),
1345 - }
1346 - }
1347 -
1348 - fn convert_assignment_expression(&self, assign: &swc::AssignExpr) -> AssignmentExpression {
1349 - AssignmentExpression {
1350 - base: self.make_base_node(assign.span),
1351 - operator: self.convert_assignment_operator(assign.op),
1352 - left: Box::new(self.convert_assign_target(&assign.left)),
1353 - right: Box::new(self.convert_expression(&assign.right)),
1354 - }
1355 - }
1356 -
1357 - // ===== Object expression =====
1358 -
1359 - fn convert_object_expression(&self, obj: &swc::ObjectLit) -> ObjectExpression {
1360 - ObjectExpression {
1361 - base: self.make_base_node(obj.span),
1362 - properties: obj
1363 - .props
1364 - .iter()
1365 - .map(|p| self.convert_prop_or_spread(p))
1366 - .collect(),
1367 - }
1368 - }
1369 -
1370 - fn convert_prop_or_spread(&self, prop: &swc::PropOrSpread) -> ObjectExpressionProperty {
1371 - match prop {
1372 - swc::PropOrSpread::Spread(s) => {
1373 - ObjectExpressionProperty::SpreadElement(SpreadElement {
1374 - base: self.make_base_node(s.span()),
1375 - argument: Box::new(self.convert_expression(&s.expr)),
1376 - })
1377 - }
1378 - swc::PropOrSpread::Prop(p) => self.convert_prop(p),
1379 - }
1380 - }
1381 -
1382 - fn convert_prop(&self, prop: &swc::Prop) -> ObjectExpressionProperty {
1383 - match prop {
1384 - swc::Prop::Shorthand(id) => {
1385 - let ident = self.convert_ident_to_identifier(id);
1386 - ObjectExpressionProperty::ObjectProperty(ObjectProperty {
1387 - base: self.make_base_node(id.span),
1388 - key: Box::new(Expression::Identifier(ident.clone())),
1389 - value: Box::new(Expression::Identifier(ident)),
1390 - computed: false,
1391 - shorthand: true,
1392 - decorators: None,
1393 - method: Some(false),
1394 - })
1395 - }
1396 - swc::Prop::KeyValue(kv) => ObjectExpressionProperty::ObjectProperty(ObjectProperty {
1397 - base: self.make_base_node(kv.span()),
1398 - key: Box::new(self.convert_prop_name(&kv.key)),
1399 - value: Box::new(self.convert_expression(&kv.value)),
1400 - computed: matches!(kv.key, swc::PropName::Computed(_)),
1401 - shorthand: false,
1402 - decorators: None,
1403 - method: Some(false),
1404 - }),
1405 - swc::Prop::Getter(g) => ObjectExpressionProperty::ObjectMethod(ObjectMethod {
1406 - base: self.make_base_node(g.span),
1407 - method: false,
1408 - kind: ObjectMethodKind::Get,
1409 - key: Box::new(self.convert_prop_name(&g.key)),
1410 - params: vec![],
1411 - body: g
1412 - .body
1413 - .as_ref()
1414 - .map(|b| self.convert_block_statement(b))
1415 - .unwrap_or_else(|| BlockStatement {
1416 - base: self.make_base_node(g.span),
1417 - body: vec![],
1418 - directives: vec![],
1419 - }),
1420 - computed: matches!(g.key, swc::PropName::Computed(_)),
1421 - id: None,
1422 - generator: false,
1423 - is_async: false,
1424 - decorators: None,
1425 - return_type: g
1426 - .type_ann
1427 - .as_ref()
1428 - .map(|_| RawNode::null()),
1429 - type_parameters: None,
1430 - predicate: None,
1431 - }),
1432 - swc::Prop::Setter(s) => ObjectExpressionProperty::ObjectMethod(ObjectMethod {
1433 - base: self.make_base_node(s.span),
1434 - method: false,
1435 - kind: ObjectMethodKind::Set,
1436 - key: Box::new(self.convert_prop_name(&s.key)),
1437 - params: vec![self.convert_pat(&s.param)],
1438 - body: s
1439 - .body
1440 - .as_ref()
1441 - .map(|b| self.convert_block_statement(b))
1442 - .unwrap_or_else(|| BlockStatement {
1443 - base: self.make_base_node(s.span),
1444 - body: vec![],
1445 - directives: vec![],
1446 - }),
1447 - computed: matches!(s.key, swc::PropName::Computed(_)),
1448 - id: None,
1449 - generator: false,
1450 - is_async: false,
1451 - decorators: None,
1452 - return_type: None,
1453 - type_parameters: None,
1454 - predicate: None,
1455 - }),
1456 - swc::Prop::Method(m) => ObjectExpressionProperty::ObjectMethod(ObjectMethod {
1457 - base: self.make_base_node(m.span()),
1458 - method: true,
1459 - kind: ObjectMethodKind::Method,
1460 - key: Box::new(self.convert_prop_name(&m.key)),
1461 - params: self.convert_params(&m.function.params),
1462 - body: m
1463 - .function
1464 - .body
1465 - .as_ref()
1466 - .map(|b| self.convert_block_statement(b))
1467 - .unwrap_or_else(|| BlockStatement {
1468 - base: self.make_base_node(m.function.span),
1469 - body: vec![],
1470 - directives: vec![],
1471 - }),
1472 - computed: matches!(m.key, swc::PropName::Computed(_)),
1473 - id: None,
1474 - generator: m.function.is_generator,
1475 - is_async: m.function.is_async,
1476 - decorators: None,
1477 - return_type: m
1478 - .function
1479 - .return_type
1480 - .as_ref()
1481 - .map(|_| RawNode::null()),
1482 - type_parameters: m
1483 - .function
1484 - .type_params
1485 - .as_ref()
1486 - .map(|_| RawNode::null()),
1487 - predicate: None,
1488 - }),
1489 - swc::Prop::Assign(a) => {
1490 - let ident = self.convert_ident_to_identifier(&a.key);
1491 - ObjectExpressionProperty::ObjectProperty(ObjectProperty {
1492 - base: self.make_base_node(a.span),
1493 - key: Box::new(Expression::Identifier(ident.clone())),
1494 - value: Box::new(Expression::AssignmentExpression(AssignmentExpression {
1495 - base: self.make_base_node(a.span),
1496 - operator: AssignmentOperator::Assign,
1497 - left: Box::new(PatternLike::Identifier(ident)),
1498 - right: Box::new(self.convert_expression(&a.value)),
1499 - })),
1500 - computed: false,
1501 - shorthand: true,
1502 - decorators: None,
1503 - method: Some(false),
1504 - })
1505 - }
1506 - }
1507 - }
1508 -
1509 - fn convert_array_expression(&self, arr: &swc::ArrayLit) -> ArrayExpression {
1510 - ArrayExpression {
1511 - base: self.make_base_node(arr.span),
1512 - elements: arr
1513 - .elems
1514 - .iter()
1515 - .map(|e| e.as_ref().map(|elem| self.convert_expr_or_spread(elem)))
1516 - .collect(),
1517 - }
1518 - }
1519 -
1520 - fn convert_template_literal(&self, tpl: &swc::Tpl) -> TemplateLiteral {
1521 - TemplateLiteral {
1522 - base: self.make_base_node(tpl.span),
1523 - quasis: tpl
1524 - .quasis
1525 - .iter()
1526 - .map(|q| TemplateElement {
1527 - base: self.make_base_node(q.span),
1528 - value: TemplateElementValue {
1529 - raw: q.raw.to_string(),
1530 - cooked: q.cooked.as_ref().map(|c| wtf8_to_string(c)),
1531 - },
1532 - tail: q.tail,
1533 - })
1534 - .collect(),
1535 - expressions: tpl
1536 - .exprs
1537 - .iter()
1538 - .map(|e| self.convert_expression(e))
1539 - .collect(),
1540 - }
1541 - }
1542 -
1543 - // ===== Class =====
1544 -
1545 - fn convert_class_decl(&self, class: &swc::ClassDecl) -> ClassDeclaration {
1546 - let c = &class.class;
1547 - ClassDeclaration {
1548 - base: self.make_base_node(c.span),
1549 - id: Some(self.convert_ident_to_identifier(&class.ident)),
1550 - super_class: c
1551 - .super_class
1552 - .as_ref()
1553 - .map(|s| Box::new(self.convert_expression(s))),
1554 - body: ClassBody {
1555 - base: self.make_base_node(c.span),
1556 - body: vec![],
1557 - },
1558 - decorators: None,
1559 - is_abstract: if c.is_abstract { Some(true) } else { None },
1560 - declare: if class.declare { Some(true) } else { None },
1561 - implements: None,
1562 - super_type_parameters: None,
1563 - type_parameters: c
1564 - .type_params
1565 - .as_ref()
1566 - .map(|_| RawNode::null()),
1567 - mixins: None,
1568 - }
1569 - }
1570 -
1571 - fn convert_class_expression(&self, class: &swc::ClassExpr) -> ClassExpression {
1572 - let c = &class.class;
1573 - ClassExpression {
1574 - base: self.make_base_node(c.span),
1575 - id: class
1576 - .ident
1577 - .as_ref()
1578 - .map(|id| self.convert_ident_to_identifier(id)),
1579 - super_class: c
1580 - .super_class
1581 - .as_ref()
1582 - .map(|s| Box::new(self.convert_expression(s))),
1583 - body: ClassBody {
1584 - base: self.make_base_node(c.span),
1585 - body: vec![],
1586 - },
1587 - decorators: None,
1588 - implements: None,
1589 - super_type_parameters: None,
1590 - type_parameters: c
1591 - .type_params
1592 - .as_ref()
1593 - .map(|_| RawNode::null()),
1594 - }
1595 - }
1596 -
1597 - // ===== JSX =====
1598 -
1599 - fn convert_jsx_element(&self, el: &swc::JSXElement) -> JSXElement {
1600 - let self_closing = el.closing.is_none();
1601 - JSXElement {
1602 - base: self.make_base_node(el.span),
1603 - opening_element: self.convert_jsx_opening_element(&el.opening, self_closing),
1604 - closing_element: el
1605 - .closing
1606 - .as_ref()
1607 - .map(|c| self.convert_jsx_closing_element(c)),
1608 - children: el
1609 - .children
1610 - .iter()
1611 - .map(|c| self.convert_jsx_child(c))
1612 - .collect(),
1613 - self_closing: Some(self_closing),
1614 - }
1615 - }
1616 -
1617 - fn convert_jsx_opening_element(
1618 - &self,
1619 - el: &swc::JSXOpeningElement,
1620 - self_closing: bool,
1621 - ) -> JSXOpeningElement {
1622 - JSXOpeningElement {
1623 - base: self.make_base_node(el.span),
1624 - name: self.convert_jsx_element_name(&el.name),
1625 - attributes: el
1626 - .attrs
1627 - .iter()
1628 - .map(|a| self.convert_jsx_attr_or_spread(a))
1629 - .collect(),
1630 - self_closing,
1631 - type_parameters: el
1632 - .type_args
1633 - .as_ref()
1634 - .map(|_| RawNode::null()),
1635 - }
1636 - }
1637 -
1638 - fn convert_jsx_closing_element(&self, el: &swc::JSXClosingElement) -> JSXClosingElement {
1639 - JSXClosingElement {
1640 - base: self.make_base_node(el.span),
1641 - name: self.convert_jsx_element_name(&el.name),
1642 - }
1643 - }
1644 -
1645 - fn convert_jsx_element_name(&self, name: &swc::JSXElementName) -> JSXElementName {
1646 - match name {
1647 - swc::JSXElementName::Ident(id) => JSXElementName::JSXIdentifier(JSXIdentifier {
1648 - base: self.make_base_node(id.span),
1649 - name: id.sym.to_string(),
1650 - }),
1651 - swc::JSXElementName::JSXMemberExpr(m) => {
1652 - JSXElementName::JSXMemberExpression(self.convert_jsx_member_expression(m))
1653 - }
1654 - swc::JSXElementName::JSXNamespacedName(ns) => {
1655 - JSXElementName::JSXNamespacedName(JSXNamespacedName {
1656 - base: self.make_base_node(ns.span()),
1657 - namespace: JSXIdentifier {
1658 - base: self.make_base_node(ns.ns.span),
1659 - name: ns.ns.sym.to_string(),
1660 - },
1661 - name: JSXIdentifier {
1662 - base: self.make_base_node(ns.name.span),
1663 - name: ns.name.sym.to_string(),
1664 - },
1665 - })
1666 - }
1667 - }
1668 - }
1669 -
1670 - fn convert_jsx_member_expression(&self, m: &swc::JSXMemberExpr) -> JSXMemberExpression {
1671 - JSXMemberExpression {
1672 - base: self.make_base_node(m.span()),
1673 - object: Box::new(self.convert_jsx_object(&m.obj)),
1674 - property: JSXIdentifier {
1675 - base: self.make_base_node(m.prop.span),
1676 - name: m.prop.sym.to_string(),
1677 - },
1678 - }
1679 - }
1680 -
1681 - fn convert_jsx_object(&self, obj: &swc::JSXObject) -> JSXMemberExprObject {
1682 - match obj {
1683 - swc::JSXObject::Ident(id) => JSXMemberExprObject::JSXIdentifier(JSXIdentifier {
1684 - base: self.make_base_node(id.span),
1685 - name: id.sym.to_string(),
1686 - }),
1687 - swc::JSXObject::JSXMemberExpr(m) => JSXMemberExprObject::JSXMemberExpression(Box::new(
1688 - self.convert_jsx_member_expression(m),
1689 - )),
1690 - }
1691 - }
1692 -
1693 - fn convert_jsx_attr_or_spread(&self, attr: &swc::JSXAttrOrSpread) -> JSXAttributeItem {
1694 - match attr {
1695 - swc::JSXAttrOrSpread::JSXAttr(a) => {
1696 - JSXAttributeItem::JSXAttribute(self.convert_jsx_attribute(a))
1697 - }
1698 - swc::JSXAttrOrSpread::SpreadElement(s) => {
1699 - JSXAttributeItem::JSXSpreadAttribute(JSXSpreadAttribute {
1700 - base: self.make_base_node(s.span()),
1701 - argument: Box::new(self.convert_expression(&s.expr)),
1702 - })
1703 - }
1704 - }
1705 - }
1706 -
1707 - fn convert_jsx_attribute(&self, attr: &swc::JSXAttr) -> JSXAttribute {
1708 - JSXAttribute {
1709 - base: self.make_base_node(attr.span),
1710 - name: self.convert_jsx_attr_name(&attr.name),
1711 - value: attr.value.as_ref().map(|v| self.convert_jsx_attr_value(v)),
1712 - }
1713 - }
1714 -
1715 - fn convert_jsx_attr_name(&self, name: &swc::JSXAttrName) -> JSXAttributeName {
1716 - match name {
1717 - swc::JSXAttrName::Ident(id) => JSXAttributeName::JSXIdentifier(JSXIdentifier {
1718 - base: self.make_base_node(id.span),
1719 - name: id.sym.to_string(),
1720 - }),
1721 - swc::JSXAttrName::JSXNamespacedName(ns) => {
1722 - JSXAttributeName::JSXNamespacedName(JSXNamespacedName {
1723 - base: self.make_base_node(ns.span()),
1724 - namespace: JSXIdentifier {
1725 - base: self.make_base_node(ns.ns.span),
1726 - name: ns.ns.sym.to_string(),
1727 - },
1728 - name: JSXIdentifier {
1729 - base: self.make_base_node(ns.name.span),
1730 - name: ns.name.sym.to_string(),
1731 - },
1732 - })
1733 - }
1734 - }
1735 - }
1736 -
1737 - fn convert_jsx_attr_value(&self, value: &swc::JSXAttrValue) -> JSXAttributeValue {
1738 - match value {
1739 - swc::JSXAttrValue::Str(s) => JSXAttributeValue::StringLiteral(StringLiteral {
1740 - base: self.make_base_node(s.span),
1741 - value: wtf8_to_string(&s.value),
1742 - }),
1743 - swc::JSXAttrValue::JSXExprContainer(ec) => {
1744 - JSXAttributeValue::JSXExpressionContainer(self.convert_jsx_expr_container(ec))
1745 - }
1746 - swc::JSXAttrValue::JSXElement(el) => {
1747 - JSXAttributeValue::JSXElement(Box::new(self.convert_jsx_element(el)))
1748 - }
1749 - swc::JSXAttrValue::JSXFragment(frag) => {
1750 - JSXAttributeValue::JSXFragment(self.convert_jsx_fragment(frag))
1751 - }
1752 - }
1753 - }
1754 -
1755 - fn convert_jsx_expr_container(&self, ec: &swc::JSXExprContainer) -> JSXExpressionContainer {
1756 - JSXExpressionContainer {
1757 - base: self.make_base_node(ec.span),
1758 - expression: match &ec.expr {
1759 - swc::JSXExpr::JSXEmptyExpr(e) => {
1760 - JSXExpressionContainerExpr::JSXEmptyExpression(JSXEmptyExpression {
1761 - base: self.make_base_node(e.span),
1762 - })
1763 - }
1764 - swc::JSXExpr::Expr(e) => {
1765 - JSXExpressionContainerExpr::Expression(Box::new(self.convert_expression(e)))
1766 - }
1767 - },
1768 - }
1769 - }
1770 -
1771 - fn convert_jsx_child(&self, child: &swc::JSXElementChild) -> JSXChild {
1772 - match child {
1773 - swc::JSXElementChild::JSXText(t) => JSXChild::JSXText(JSXText {
1774 - base: self.make_base_node(t.span),
1775 - value: t.value.to_string(),
1776 - }),
1777 - swc::JSXElementChild::JSXExprContainer(ec) => {
1778 - JSXChild::JSXExpressionContainer(self.convert_jsx_expr_container(ec))
1779 - }
1780 - swc::JSXElementChild::JSXSpreadChild(s) => JSXChild::JSXSpreadChild(JSXSpreadChild {
1781 - base: self.make_base_node(s.span),
1782 - expression: Box::new(self.convert_expression(&s.expr)),
1783 - }),
1784 - swc::JSXElementChild::JSXElement(el) => {
1785 - JSXChild::JSXElement(Box::new(self.convert_jsx_element(el)))
1786 - }
1787 - swc::JSXElementChild::JSXFragment(frag) => {
1788 - JSXChild::JSXFragment(self.convert_jsx_fragment(frag))
1789 - }
1790 - }
1791 - }
1792 -
1793 - fn convert_jsx_fragment(&self, frag: &swc::JSXFragment) -> JSXFragment {
1794 - JSXFragment {
1795 - base: self.make_base_node(frag.span),
1796 - opening_fragment: JSXOpeningFragment {
1797 - base: self.make_base_node(frag.opening.span),
1798 - },
1799 - closing_fragment: JSXClosingFragment {
1800 - base: self.make_base_node(frag.closing.span),
1801 - },
1802 - children: frag
1803 - .children
1804 - .iter()
1805 - .map(|c| self.convert_jsx_child(c))
1806 - .collect(),
1807 - }
1808 - }
1809 -
1810 - // ===== Import/Export =====
1811 -
1812 - fn convert_import_declaration(&self, decl: &swc::ImportDecl) -> ImportDeclaration {
1813 - ImportDeclaration {
1814 - base: self.make_base_node(decl.span),
1815 - specifiers: decl
1816 - .specifiers
1817 - .iter()
1818 - .map(|s| self.convert_import_specifier(s))
1819 - .collect(),
1820 - source: StringLiteral {
1821 - base: self.make_base_node(decl.src.span),
1822 - value: wtf8_to_string(&decl.src.value),
1823 - },
1824 - import_kind: if decl.type_only {
1825 - Some(ImportKind::Type)
1826 - } else {
1827 - Some(ImportKind::Value)
1828 - },
1829 - assertions: None,
1830 - attributes: decl
1831 - .with
1832 - .as_ref()
1833 - .map(|with| self.convert_object_lit_to_import_attributes(with)),
1834 - }
1835 - }
1836 -
1837 - fn convert_object_lit_to_import_attributes(
1838 - &self,
1839 - obj: &swc::ObjectLit,
1840 - ) -> Vec<ImportAttribute> {
1841 - obj.props
1842 - .iter()
1843 - .filter_map(|prop| {
1844 - if let swc::PropOrSpread::Prop(p) = prop {
1845 - if let swc::Prop::KeyValue(kv) = &**p {
1846 - let (key_name, key_span) = match &kv.key {
1847 - swc::PropName::Ident(id) => (id.sym.to_string(), id.span),
1848 - swc::PropName::Str(s) => (wtf8_to_string(&s.value), s.span),
1849 - swc::PropName::Num(n) => (n.value.to_string(), n.span),
1850 - _ => return None,
1851 - };
1852 - if let swc::Expr::Lit(swc::Lit::Str(s)) = &*kv.value {
1853 - return Some(ImportAttribute {
1854 - base: self.make_base_node(kv.span()),
1855 - key: Identifier {
1856 - base: self.make_base_node(key_span),
1857 - name: key_name,
1858 - type_annotation: None,
1859 - optional: None,
1860 - decorators: None,
1861 - },
1862 - value: StringLiteral {
1863 - base: self.make_base_node(s.span),
1864 - value: wtf8_to_string(&s.value),
1865 - },
1866 - });
1867 - }
1868 - }
1869 - }
1870 - None
1871 - })
1872 - .collect()
1873 - }
1874 -
1875 - fn convert_import_specifier(&self, spec: &swc::ImportSpecifier) -> ImportSpecifier {
1876 - match spec {
1877 - swc::ImportSpecifier::Named(s) => {
1878 - let local = self.convert_ident_to_identifier(&s.local);
1879 - let imported = s
1880 - .imported
1881 - .as_ref()
1882 - .map(|i| match i {
1883 - swc::ModuleExportName::Ident(id) => {
1884 - ModuleExportName::Identifier(self.convert_ident_to_identifier(id))
1885 - }
1886 - swc::ModuleExportName::Str(s) => {
1887 - ModuleExportName::StringLiteral(StringLiteral {
1888 - base: self.make_base_node(s.span),
1889 - value: wtf8_to_string(&s.value),
1890 - })
1891 - }
1892 - })
1893 - .unwrap_or_else(|| ModuleExportName::Identifier(local.clone()));
1894 - ImportSpecifier::ImportSpecifier(ImportSpecifierData {
1895 - base: self.make_base_node(s.span),
1896 - local,
1897 - imported,
1898 - import_kind: if s.is_type_only {
1899 - Some(ImportKind::Type)
1900 - } else {
1901 - Some(ImportKind::Value)
1902 - },
1903 - })
1904 - }
1905 - swc::ImportSpecifier::Default(s) => {
1906 - ImportSpecifier::ImportDefaultSpecifier(ImportDefaultSpecifierData {
1907 - base: self.make_base_node(s.span),
1908 - local: self.convert_ident_to_identifier(&s.local),
1909 - })
1910 - }
1911 - swc::ImportSpecifier::Namespace(s) => {
1912 - ImportSpecifier::ImportNamespaceSpecifier(ImportNamespaceSpecifierData {
1913 - base: self.make_base_node(s.span),
1914 - local: self.convert_ident_to_identifier(&s.local),
1915 - })
1916 - }
1917 - }
1918 - }
1919 -
1920 - fn convert_export_decl(&self, decl: &swc::ExportDecl) -> ExportNamedDeclaration {
1921 - ExportNamedDeclaration {
1922 - base: self.make_base_node(decl.span),
1923 - declaration: Some(Box::new(self.convert_decl_to_declaration(&decl.decl))),
1924 - specifiers: vec![],
1925 - source: None,
1926 - export_kind: Some(ExportKind::Value),
1927 - assertions: None,
1928 - attributes: None,
1929 - }
1930 - }
1931 -
1932 - fn convert_export_named(&self, decl: &swc::NamedExport) -> ExportNamedDeclaration {
1933 - ExportNamedDeclaration {
1934 - base: self.make_base_node(decl.span),
1935 - declaration: None,
1936 - specifiers: decl
1937 - .specifiers
1938 - .iter()
1939 - .map(|s| self.convert_export_specifier(s))
1940 - .collect(),
1941 - source: decl.src.as_ref().map(|s| StringLiteral {
1942 - base: self.make_base_node(s.span),
1943 - value: wtf8_to_string(&s.value),
1944 - }),
1945 - export_kind: if decl.type_only {
1946 - Some(ExportKind::Type)
1947 - } else {
1948 - Some(ExportKind::Value)
1949 - },
1950 - assertions: None,
1951 - attributes: decl
1952 - .with
1953 - .as_ref()
1954 - .map(|with| self.convert_object_lit_to_import_attributes(with)),
1955 - }
1956 - }
1957 -
1958 - fn convert_export_default_decl(
1959 - &self,
1960 - decl: &swc::ExportDefaultDecl,
1961 - ) -> ExportDefaultDeclaration {
1962 - let declaration = match &decl.decl {
1963 - swc::DefaultDecl::Fn(f) => {
1964 - let func = &f.function;
1965 - let body = func
1966 - .body
1967 - .as_ref()
1968 - .map(|b| self.convert_block_statement(b))
1969 - .unwrap_or_else(|| BlockStatement {
1970 - base: self.make_base_node(func.span),
1971 - body: vec![],
1972 - directives: vec![],
1973 - });
1974 - ExportDefaultDecl::FunctionDeclaration(FunctionDeclaration {
1975 - base: self.make_base_node(func.span),
1976 - id: f
1977 - .ident
1978 - .as_ref()
1979 - .map(|id| self.convert_ident_to_identifier(id)),
1980 - params: self.convert_params(&func.params),
1981 - body,
1982 - generator: func.is_generator,
1983 - is_async: func.is_async,
1984 - declare: None,
1985 - return_type: func
1986 - .return_type
1987 - .as_ref()
1988 - .map(|_| RawNode::null()),
1989 - type_parameters: func
1990 - .type_params
1991 - .as_ref()
1992 - .map(|_| RawNode::null()),
1993 - predicate: None,
1994 - component_declaration: false,
1995 - hook_declaration: false,
1996 - })
1997 - }
1998 - swc::DefaultDecl::Class(c) => {
1999 - let class = &c.class;
2000 - ExportDefaultDecl::ClassDeclaration(ClassDeclaration {
2001 - base: self.make_base_node(class.span),
2002 - id: c
2003 - .ident
2004 - .as_ref()
2005 - .map(|id| self.convert_ident_to_identifier(id)),
2006 - super_class: class
2007 - .super_class
2008 - .as_ref()
2009 - .map(|s| Box::new(self.convert_expression(s))),
2010 - body: ClassBody {
2011 - base: self.make_base_node(class.span),
2012 - body: vec![],
2013 - },
2014 - decorators: None,
2015 - is_abstract: if class.is_abstract { Some(true) } else { None },
2016 - declare: None,
2017 - implements: None,
2018 - super_type_parameters: None,
2019 - type_parameters: class
2020 - .type_params
2021 - .as_ref()
2022 - .map(|_| RawNode::null()),
2023 - mixins: None,
2024 - })
2025 - }
2026 - swc::DefaultDecl::TsInterfaceDecl(_) => {
2027 - ExportDefaultDecl::Expression(Box::new(Expression::NullLiteral(NullLiteral {
2028 - base: self.make_base_node(decl.span),
2029 - })))
2030 - }
2031 - };
2032 - ExportDefaultDeclaration {
2033 - base: self.make_base_node(decl.span),
2034 - declaration: Box::new(declaration),
2035 - export_kind: None,
2036 - }
2037 - }
2038 -
2039 - fn convert_export_default_expr(
2040 - &self,
2041 - decl: &swc::ExportDefaultExpr,
2042 - ) -> ExportDefaultDeclaration {
2043 - ExportDefaultDeclaration {
2044 - base: self.make_base_node(decl.span),
2045 - declaration: Box::new(ExportDefaultDecl::Expression(Box::new(
2046 - self.convert_expression(&decl.expr),
2047 - ))),
2048 - export_kind: None,
2049 - }
2050 - }
2051 -
2052 - fn convert_export_all(&self, decl: &swc::ExportAll) -> ExportAllDeclaration {
2053 - ExportAllDeclaration {
2054 - base: self.make_base_node(decl.span),
2055 - source: StringLiteral {
2056 - base: self.make_base_node(decl.src.span),
2057 - value: wtf8_to_string(&decl.src.value),
2058 - },
2059 - export_kind: if decl.type_only {
2060 - Some(ExportKind::Type)
2061 - } else {
2062 - Some(ExportKind::Value)
2063 - },
2064 - assertions: None,
2065 - attributes: decl
2066 - .with
2067 - .as_ref()
2068 - .map(|with| self.convert_object_lit_to_import_attributes(with)),
2069 - }
2070 - }
2071 -
2072 - fn convert_decl_to_declaration(&self, decl: &swc::Decl) -> Declaration {
2073 - match decl {
2074 - swc::Decl::Var(v) => {
2075 - Declaration::VariableDeclaration(self.convert_variable_declaration(v))
2076 - }
2077 - swc::Decl::Fn(f) => Declaration::FunctionDeclaration(self.convert_fn_decl(f)),
2078 - swc::Decl::Class(c) => Declaration::ClassDeclaration(self.convert_class_decl(c)),
2079 - swc::Decl::TsTypeAlias(d) => {
2080 - Declaration::TSTypeAliasDeclaration(self.convert_ts_type_alias(d))
2081 - }
2082 - swc::Decl::TsInterface(d) => {
2083 - Declaration::TSInterfaceDeclaration(self.convert_ts_interface(d))
2084 - }
2085 - swc::Decl::TsEnum(d) => Declaration::TSEnumDeclaration(self.convert_ts_enum(d)),
2086 - swc::Decl::TsModule(d) => Declaration::TSModuleDeclaration(self.convert_ts_module(d)),
2087 - swc::Decl::Using(u) => Declaration::VariableDeclaration(self.convert_using_decl(u)),
2088 - }
2089 - }
2090 -
2091 - fn convert_export_specifier(&self, spec: &swc::ExportSpecifier) -> ExportSpecifier {
2092 - match spec {
2093 - swc::ExportSpecifier::Named(s) => {
2094 - let local = self.convert_module_export_name(&s.orig);
2095 - let exported = s
2096 - .exported
2097 - .as_ref()
2098 - .map(|e| self.convert_module_export_name(e))
2099 - .unwrap_or_else(|| local.clone());
2100 - ExportSpecifier::ExportSpecifier(ExportSpecifierData {
2101 - base: self.make_base_node(s.span),
2102 - local,
2103 - exported,
2104 - export_kind: if s.is_type_only {
2105 - Some(ExportKind::Type)
2106 - } else {
2107 - Some(ExportKind::Value)
2108 - },
2109 - })
2110 - }
2111 - swc::ExportSpecifier::Default(s) => {
2112 - ExportSpecifier::ExportDefaultSpecifier(ExportDefaultSpecifierData {
2113 - base: self.make_base_node(s.exported.span),
2114 - exported: self.convert_ident_to_identifier(&s.exported),
2115 - })
2116 - }
2117 - swc::ExportSpecifier::Namespace(s) => {
2118 - ExportSpecifier::ExportNamespaceSpecifier(ExportNamespaceSpecifierData {
2119 - base: self.make_base_node(s.span),
2120 - exported: self.convert_module_export_name(&s.name),
2121 - })
2122 - }
2123 - }
2124 - }
2125 -
2126 - fn convert_module_export_name(&self, name: &swc::ModuleExportName) -> ModuleExportName {
2127 - match name {
2128 - swc::ModuleExportName::Ident(id) => {
2129 - ModuleExportName::Identifier(self.convert_ident_to_identifier(id))
2130 - }
2131 - swc::ModuleExportName::Str(s) => ModuleExportName::StringLiteral(StringLiteral {
2132 - base: self.make_base_node(s.span),
2133 - value: wtf8_to_string(&s.value),
2134 - }),
2135 - }
2136 - }
2137 -
2138 - // ===== TS declarations =====
2139 -
2140 - fn convert_ts_type_alias(&self, d: &swc::TsTypeAliasDecl) -> TSTypeAliasDeclaration {
2141 - TSTypeAliasDeclaration {
2142 - base: self.make_base_node(d.span),
2143 - id: self.convert_ident_to_identifier(&d.id),
2144 - type_annotation: RawNode::null(),
2145 - type_parameters: d
2146 - .type_params
2147 - .as_ref()
2148 - .map(|_| RawNode::null()),
2149 - declare: if d.declare { Some(true) } else { None },
2150 - }
2151 - }
2152 -
2153 - fn convert_ts_interface(&self, d: &swc::TsInterfaceDecl) -> TSInterfaceDeclaration {
2154 - TSInterfaceDeclaration {
2155 - base: self.make_base_node(d.span),
2156 - id: self.convert_ident_to_identifier(&d.id),
2157 - body: RawNode::null(),
2158 - type_parameters: d
2159 - .type_params
2160 - .as_ref()
2161 - .map(|_| RawNode::null()),
2162 - extends: if d.extends.is_empty() {
2163 - None
2164 - } else {
2165 - Some(vec![])
2166 - },
2167 - declare: if d.declare { Some(true) } else { None },
2168 - }
2169 - }
2170 -
2171 - fn convert_ts_enum(&self, d: &swc::TsEnumDecl) -> TSEnumDeclaration {
2172 - TSEnumDeclaration {
2173 - base: self.make_base_node(d.span),
2174 - id: self.convert_ident_to_identifier(&d.id),
2175 - members: vec![],
2176 - declare: if d.declare { Some(true) } else { None },
2177 - is_const: if d.is_const { Some(true) } else { None },
2178 - }
2179 - }
2180 -
2181 - fn convert_ts_module(&self, d: &swc::TsModuleDecl) -> TSModuleDeclaration {
2182 - TSModuleDeclaration {
2183 - base: self.make_base_node(d.span),
2184 - id: RawNode::null(),
2185 - body: RawNode::null(),
2186 - declare: if d.declare { Some(true) } else { None },
2187 - global: if d.global { Some(true) } else { None },
2188 - }
2189 - }
2190 -
2191 - // ===== TS type JSON (binding ident annotations) =====
2192 -
2193 - fn convert_ts_type_to_json(&self, ty: &swc::TsType) -> Option<serde_json::Value> {
2194 - match ty {
2195 - swc::TsType::TsKeywordType(k) if k.kind == swc::TsKeywordTypeKind::TsNumberKeyword => {
2196 - Some(serde_json::json!({ "type": "TSNumberKeyword" }))
2197 - }
2198 - // Skip generics: `convert_ts_type_annotation_from_json` ignores
2199 - // type params, so silently emitting `Foo` for `Foo<T>` loses
2200 - // information. Fall back to `Null` instead.
2201 - swc::TsType::TsTypeRef(r) if r.type_params.is_none() => match &r.type_name {
2202 - swc::TsEntityName::Ident(id) => Some(serde_json::json!({
2203 - "type": "TSTypeReference",
2204 - "typeName": {
2205 - "type": "Identifier",
2206 - "name": id.sym.to_string()
2207 - }
2208 - })),
2209 - _ => None,
2210 - },
2211 - _ => None,
2212 - }
2213 - }
2214 -
2215 - fn convert_ts_type_ann_to_json(&self, ann: &swc::TsTypeAnn) -> Option<serde_json::Value> {
2216 - Some(serde_json::json!({
2217 - "type": "TSTypeAnnotation",
2218 - "typeAnnotation": self.convert_ts_type_to_json(&ann.type_ann)?,
2219 - }))
2220 - }
2221 -
2222 - // ===== Identifiers =====
2223 -
2224 - fn convert_ident_to_identifier(&self, id: &swc::Ident) -> Identifier {
2225 - Identifier {
2226 - base: self.make_base_node(id.span),
2227 - name: id.sym.to_string(),
2228 - type_annotation: None,
2229 - optional: if id.optional { Some(true) } else { None },
2230 - decorators: None,
2231 - }
2232 - }
2233 -
2234 - fn convert_binding_ident(&self, id: &swc::BindingIdent) -> Identifier {
2235 - Identifier {
2236 - base: self.make_base_node(id.id.span),
2237 - name: id.id.sym.to_string(),
2238 - type_annotation: id.type_ann.as_ref().map(|ann| {
2239 - RawNode::from_value(
2240 - &self
2241 - .convert_ts_type_ann_to_json(ann)
2242 - .unwrap_or(serde_json::Value::Null),
2243 - )
2244 - }),
2245 - optional: if id.id.optional { Some(true) } else { None },
2246 - decorators: None,
2247 - }
2248 - }
2249 -
2250 - fn convert_prop_name(&self, key: &swc::PropName) -> Expression {
2251 - match key {
2252 - swc::PropName::Ident(id) => Expression::Identifier(Identifier {
2253 - base: self.make_base_node(id.span),
2254 - name: id.sym.to_string(),
2255 - type_annotation: None,
2256 - optional: None,
2257 - decorators: None,
2258 - }),
2259 - swc::PropName::Str(s) => Expression::StringLiteral(StringLiteral {
2260 - base: self.make_base_node(s.span),
2261 - value: wtf8_to_string(&s.value),
2262 - }),
2263 - swc::PropName::Num(n) => Expression::NumericLiteral(NumericLiteral {
2264 - base: self.make_base_node(n.span),
2265 - value: n.value,
2266 - extra: None,
2267 - }),
2268 - swc::PropName::Computed(c) => self.convert_expression(&c.expr),
2269 - swc::PropName::BigInt(b) => Expression::BigIntLiteral(BigIntLiteral {
2270 - base: self.make_base_node(b.span),
2271 - value: b.value.to_string(),
2272 - }),
2273 - }
2274 - }
2275 -
2276 - // ===== Operators =====
2277 -
2278 - fn convert_binary_operator(&self, op: swc::BinaryOp) -> BinaryOperator {
2279 - match op {
2280 - swc::BinaryOp::EqEq => BinaryOperator::Eq,
2281 - swc::BinaryOp::NotEq => BinaryOperator::Neq,
2282 - swc::BinaryOp::EqEqEq => BinaryOperator::StrictEq,
2283 - swc::BinaryOp::NotEqEq => BinaryOperator::StrictNeq,
2284 - swc::BinaryOp::Lt => BinaryOperator::Lt,
2285 - swc::BinaryOp::LtEq => BinaryOperator::Lte,
2286 - swc::BinaryOp::Gt => BinaryOperator::Gt,
2287 - swc::BinaryOp::GtEq => BinaryOperator::Gte,
2288 - swc::BinaryOp::LShift => BinaryOperator::Shl,
2289 - swc::BinaryOp::RShift => BinaryOperator::Shr,
2290 - swc::BinaryOp::ZeroFillRShift => BinaryOperator::UShr,
2291 - swc::BinaryOp::Add => BinaryOperator::Add,
2292 - swc::BinaryOp::Sub => BinaryOperator::Sub,
2293 - swc::BinaryOp::Mul => BinaryOperator::Mul,
2294 - swc::BinaryOp::Div => BinaryOperator::Div,
2295 - swc::BinaryOp::Mod => BinaryOperator::Rem,
2296 - swc::BinaryOp::Exp => BinaryOperator::Exp,
2297 - swc::BinaryOp::BitOr => BinaryOperator::BitOr,
2298 - swc::BinaryOp::BitXor => BinaryOperator::BitXor,
2299 - swc::BinaryOp::BitAnd => BinaryOperator::BitAnd,
2300 - swc::BinaryOp::In => BinaryOperator::In,
2301 - swc::BinaryOp::InstanceOf => BinaryOperator::Instanceof,
2302 - swc::BinaryOp::LogicalOr
2303 - | swc::BinaryOp::LogicalAnd
2304 - | swc::BinaryOp::NullishCoalescing => BinaryOperator::Eq,
2305 - }
2306 - }
2307 -
2308 - fn try_convert_logical_operator(&self, op: swc::BinaryOp) -> Option<LogicalOperator> {
2309 - match op {
2310 - swc::BinaryOp::LogicalOr => Some(LogicalOperator::Or),
2311 - swc::BinaryOp::LogicalAnd => Some(LogicalOperator::And),
2312 - swc::BinaryOp::NullishCoalescing => Some(LogicalOperator::NullishCoalescing),
2313 - _ => None,
2314 - }
2315 - }
2316 -
2317 - fn convert_unary_operator(&self, op: swc::UnaryOp) -> UnaryOperator {
2318 - match op {
2319 - swc::UnaryOp::Minus => UnaryOperator::Neg,
2320 - swc::UnaryOp::Plus => UnaryOperator::Plus,
2321 - swc::UnaryOp::Bang => UnaryOperator::Not,
2322 - swc::UnaryOp::Tilde => UnaryOperator::BitNot,
2323 - swc::UnaryOp::TypeOf => UnaryOperator::TypeOf,
2324 - swc::UnaryOp::Void => UnaryOperator::Void,
2325 - swc::UnaryOp::Delete => UnaryOperator::Delete,
2326 - }
2327 - }
2328 -
2329 - fn convert_update_operator(&self, op: swc::UpdateOp) -> UpdateOperator {
2330 - match op {
2331 - swc::UpdateOp::PlusPlus => UpdateOperator::Increment,
2332 - swc::UpdateOp::MinusMinus => UpdateOperator::Decrement,
2333 - }
2334 - }
2335 -
2336 - fn convert_assignment_operator(&self, op: swc::AssignOp) -> AssignmentOperator {
2337 - match op {
2338 - swc::AssignOp::Assign => AssignmentOperator::Assign,
2339 - swc::AssignOp::AddAssign => AssignmentOperator::AddAssign,
2340 - swc::AssignOp::SubAssign => AssignmentOperator::SubAssign,
2341 - swc::AssignOp::MulAssign => AssignmentOperator::MulAssign,
2342 - swc::AssignOp::DivAssign => AssignmentOperator::DivAssign,
2343 - swc::AssignOp::ModAssign => AssignmentOperator::RemAssign,
2344 - swc::AssignOp::ExpAssign => AssignmentOperator::ExpAssign,
2345 - swc::AssignOp::LShiftAssign => AssignmentOperator::ShlAssign,
2346 - swc::AssignOp::RShiftAssign => AssignmentOperator::ShrAssign,
2347 - swc::AssignOp::ZeroFillRShiftAssign => AssignmentOperator::UShrAssign,
2348 - swc::AssignOp::BitOrAssign => AssignmentOperator::BitOrAssign,
2349 - swc::AssignOp::BitXorAssign => AssignmentOperator::BitXorAssign,
2350 - swc::AssignOp::BitAndAssign => AssignmentOperator::BitAndAssign,
2351 - swc::AssignOp::OrAssign => AssignmentOperator::OrAssign,
2352 - swc::AssignOp::AndAssign => AssignmentOperator::AndAssign,
2353 - swc::AssignOp::NullishAssign => AssignmentOperator::NullishAssign,
2354 - }
2355 - }
2356 -}
compiler/crates/react_compiler_swc/src/convert_ast_reverse.rs deleted
-2705
@@ -1,2705 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -//! Reverse AST converter: react_compiler_ast (Babel format) → SWC AST.
7 -//!
8 -//! This is the inverse of `convert_ast.rs`. It takes a `react_compiler_ast::File`
9 -//! (which represents the compiler's Babel-compatible output) and produces SWC AST
10 -//! nodes suitable for code generation via `swc_codegen`.
11 -
12 -use react_compiler_ast::common::BaseNode;
13 -use react_compiler_ast::common::Comment as BabelComment;
14 -use react_compiler_ast::declarations::ExportAllDeclaration;
15 -use react_compiler_ast::declarations::ExportDefaultDecl as BabelExportDefaultDecl;
16 -use react_compiler_ast::declarations::ExportDefaultDeclaration;
17 -use react_compiler_ast::declarations::ExportKind;
18 -use react_compiler_ast::declarations::ExportNamedDeclaration;
19 -use react_compiler_ast::declarations::ImportDeclaration;
20 -use react_compiler_ast::declarations::ImportKind;
21 -use react_compiler_ast::expressions::Expression as BabelExpr;
22 -use react_compiler_ast::expressions::{self as babel_expr};
23 -use react_compiler_ast::operators::*;
24 -use react_compiler_ast::patterns::*;
25 -use react_compiler_ast::statements::Statement as BabelStmt;
26 -use react_compiler_ast::statements::{self as babel_stmt};
27 -use swc_atoms::Atom;
28 -use swc_atoms::Wtf8Atom;
29 -use swc_common::BytePos;
30 -use swc_common::DUMMY_SP;
31 -use swc_common::Span;
32 -use swc_common::Spanned;
33 -use swc_common::SyntaxContext;
34 -use swc_common::comments::Comment as SwcComment;
35 -use swc_common::comments::CommentKind;
36 -use swc_common::comments::Comments;
37 -use swc_common::comments::SingleThreadedComments;
38 -use swc_ecma_ast::*;
39 -
40 -/// Result of converting a Babel AST back to SWC, including extracted comments.
41 -pub struct SwcConversionResult {
42 - pub module: Module,
43 - pub comments: SingleThreadedComments,
44 -}
45 -
46 -/// Convert a `react_compiler_ast::File` into an SWC `Module` and extracted comments.
47 -pub fn convert_program_to_swc(file: &react_compiler_ast::File) -> SwcConversionResult {
48 - convert_program_to_swc_with_source(file, None)
49 -}
50 -
51 -/// Convert a `react_compiler_ast::File` into an SWC `Module` and extracted comments.
52 -/// When `source_text` is provided, type declarations can be extracted from the
53 -/// original source for perfect fidelity.
54 -pub fn convert_program_to_swc_with_source(
55 - file: &react_compiler_ast::File,
56 - source_text: Option<&str>,
57 -) -> SwcConversionResult {
58 - let ctx = ReverseCtx {
59 - comments: SingleThreadedComments::default(),
60 - source_text: source_text.map(|s| s.to_string()),
61 - };
62 - let module = ctx.convert_program(&file.program);
63 - SwcConversionResult {
64 - module,
65 - comments: ctx.comments,
66 - }
67 -}
68 -
69 -struct ReverseCtx {
70 - comments: SingleThreadedComments,
71 - source_text: Option<String>,
72 -}
73 -
74 -impl ReverseCtx {
75 - /// Convert a BaseNode's start/end to an SWC Span, and extract any comments.
76 - fn span(&self, base: &BaseNode) -> Span {
77 - let span = match (base.start, base.end) {
78 - (Some(start), Some(end)) => Span::new(BytePos(start), BytePos(end)),
79 - _ => DUMMY_SP,
80 - };
81 - self.extract_comments(base, span);
82 - span
83 - }
84 -
85 - /// Convert a BaseNode's start/end to an SWC Span without extracting comments.
86 - /// Use this for sub-nodes where comments should not be duplicated.
87 - fn span_no_comments(&self, base: &BaseNode) -> Span {
88 - match (base.start, base.end) {
89 - (Some(start), Some(end)) => Span::new(BytePos(start), BytePos(end)),
90 - _ => DUMMY_SP,
91 - }
92 - }
93 -
94 - /// Convert a Babel comment to an SWC comment.
95 - fn convert_babel_comment(babel_comment: &BabelComment) -> SwcComment {
96 - let (kind, text) = match babel_comment {
97 - BabelComment::CommentBlock(data) => (CommentKind::Block, &data.value),
98 - BabelComment::CommentLine(data) => (CommentKind::Line, &data.value),
99 - };
100 - SwcComment {
101 - kind,
102 - span: DUMMY_SP,
103 - text: Atom::from(text.as_str()),
104 - }
105 - }
106 -
107 - /// Extract comments from a BaseNode and register them with the SWC comments store.
108 - fn extract_comments(&self, base: &BaseNode, span: Span) {
109 - if let Some(ref leading) = base.leading_comments {
110 - let pos = span.lo;
111 - for c in leading {
112 - self.comments
113 - .add_leading(pos, Self::convert_babel_comment(c));
114 - }
115 - }
116 - if let Some(ref trailing) = base.trailing_comments {
117 - let pos = span.hi;
118 - for c in trailing {
119 - self.comments
120 - .add_trailing(pos, Self::convert_babel_comment(c));
121 - }
122 - }
123 - if let Some(ref inner) = base.inner_comments {
124 - // Inner comments are typically leading comments of the next token
125 - let pos = span.lo;
126 - for c in inner {
127 - self.comments
128 - .add_leading(pos, Self::convert_babel_comment(c));
129 - }
130 - }
131 - }
132 -
133 - fn atom(&self, s: &str) -> Atom {
134 - Atom::from(s)
135 - }
136 -
137 - fn wtf8(&self, s: &str) -> Wtf8Atom {
138 - Wtf8Atom::from(s)
139 - }
140 -
141 - /// Escape non-ASCII characters and special characters (like tab) in a string
142 - /// value to \uXXXX or \xXX sequences, matching Babel's codegen output.
143 - /// Returns the raw string representation wrapped in double quotes.
144 - fn escape_string_raw(&self, value: &str) -> Option<Atom> {
145 - let mut needs_escape = false;
146 - for ch in value.chars() {
147 - if !ch.is_ascii() || ch == '\t' || ch == '\'' || ch == '"' || ch == '\\' {
148 - needs_escape = true;
149 - break;
150 - }
151 - }
152 - if !needs_escape {
153 - return None;
154 - }
155 - let mut escaped = String::with_capacity(value.len() + 16);
156 - escaped.push('"');
157 - for ch in value.chars() {
158 - match ch {
159 - '"' => escaped.push_str("\\\""),
160 - '\\' => escaped.push_str("\\\\"),
161 - '\n' => escaped.push_str("\\n"),
162 - '\r' => escaped.push_str("\\r"),
163 - '\t' => escaped.push_str("\\t"),
164 - c if !c.is_ascii() => {
165 - // Encode using \uXXXX (or surrogate pairs for chars > U+FFFF)
166 - let mut buf = [0u16; 2];
167 - let encoded = c.encode_utf16(&mut buf);
168 - for unit in encoded {
169 - escaped.push_str(&format!("\\u{:04X}", unit));
170 - }
171 - }
172 - c => escaped.push(c),
173 - }
174 - }
175 - escaped.push('"');
176 - Some(Atom::from(escaped.as_str()))
177 - }
178 -
179 - /// Extract the original source text for a node and re-parse it as a
180 - /// statement using SWC's TypeScript parser. This is used for type
181 - /// declarations (type aliases, interfaces, enums) that the compiler
182 - /// preserves verbatim from the original source.
183 - fn extract_source_stmt(&self, base: &react_compiler_ast::common::BaseNode) -> Option<Stmt> {
184 - let source = self.source_text.as_deref()?;
185 - let start = base.start? as usize;
186 - let end = base.end? as usize;
187 - // SWC BytePos is 1-based
188 - let start_idx = start.saturating_sub(1);
189 - let end_idx = end.saturating_sub(1);
190 - if start_idx >= source.len() || end_idx > source.len() || start_idx >= end_idx {
191 - return None;
192 - }
193 - let text = &source[start_idx..end_idx];
194 - self.parse_ts_stmt(text, base)
195 - }
196 -
197 - /// Parse a string as a TypeScript statement using SWC's parser.
198 - fn parse_ts_stmt(
199 - &self,
200 - text: &str,
201 - base: &react_compiler_ast::common::BaseNode,
202 - ) -> Option<Stmt> {
203 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
204 - let fm = cm.new_source_file(
205 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
206 - text.to_string(),
207 - );
208 - let mut errors = vec![];
209 - let module = swc_ecma_parser::parse_file_as_module(
210 - &fm,
211 - swc_ecma_parser::Syntax::Typescript(swc_ecma_parser::TsSyntax {
212 - tsx: true,
213 - ..Default::default()
214 - }),
215 - swc_ecma_ast::EsVersion::latest(),
216 - None,
217 - &mut errors,
218 - )
219 - .ok()?;
220 -
221 - if let Some(item) = module.body.into_iter().next() {
222 - match item {
223 - ModuleItem::Stmt(stmt) => {
224 - // Assign the original span so blank line computation works
225 - let span = self.span(base);
226 - return Some(self.assign_span_to_stmt(stmt, span));
227 - }
228 - ModuleItem::ModuleDecl(_) => {}
229 - }
230 - }
231 - None
232 - }
233 -
234 - /// Assign a span to a statement's outermost node.
235 - fn assign_span_to_stmt(&self, stmt: Stmt, span: Span) -> Stmt {
236 - match stmt {
237 - Stmt::Decl(Decl::TsTypeAlias(mut d)) => {
238 - d.span = span;
239 - Stmt::Decl(Decl::TsTypeAlias(d))
240 - }
241 - Stmt::Decl(Decl::TsInterface(mut d)) => {
242 - d.span = span;
243 - Stmt::Decl(Decl::TsInterface(d))
244 - }
245 - Stmt::Decl(Decl::TsEnum(mut d)) => {
246 - d.span = span;
247 - Stmt::Decl(Decl::TsEnum(d))
248 - }
249 - other => other,
250 - }
251 - }
252 -
253 - fn ident(&self, name: &str, span: Span) -> Ident {
254 - Ident {
255 - sym: self.atom(name),
256 - span,
257 - ctxt: SyntaxContext::empty(),
258 - optional: false,
259 - }
260 - }
261 -
262 - fn ident_name(&self, name: &str, span: Span) -> IdentName {
263 - IdentName {
264 - sym: self.atom(name),
265 - span,
266 - }
267 - }
268 -
269 - fn binding_ident(&self, name: &str, span: Span) -> BindingIdent {
270 - BindingIdent {
271 - id: self.ident(name, span),
272 - type_ann: None,
273 - }
274 - }
275 -
276 - // ===== Program =====
277 -
278 - fn convert_program(&self, program: &react_compiler_ast::Program) -> Module {
279 - let mut body: Vec<ModuleItem> = Vec::new();
280 -
281 - // Convert directives to expression statements at the beginning
282 - for dir in &program.directives {
283 - let span = self.span(&dir.base);
284 - let str_span = self.span(&dir.value.base);
285 - body.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
286 - span,
287 - expr: Box::new(Expr::Lit(Lit::Str(Str {
288 - span: str_span,
289 - value: self.wtf8(&dir.value.value),
290 - raw: None,
291 - }))),
292 - })));
293 - }
294 -
295 - for s in &program.body {
296 - body.push(self.convert_statement_to_module_item(s));
297 - }
298 -
299 - Module {
300 - span: DUMMY_SP,
301 - body,
302 - shebang: None,
303 - }
304 - }
305 -
306 - fn convert_statement_to_module_item(&self, stmt: &BabelStmt) -> ModuleItem {
307 - match stmt {
308 - BabelStmt::ImportDeclaration(d) => {
309 - ModuleItem::ModuleDecl(ModuleDecl::Import(self.convert_import_declaration(d)))
310 - }
311 - BabelStmt::ExportNamedDeclaration(d) => self.convert_export_named_to_module_item(d),
312 - BabelStmt::ExportDefaultDeclaration(d) => self.convert_export_default_to_module_item(d),
313 - BabelStmt::ExportAllDeclaration(d) => ModuleItem::ModuleDecl(ModuleDecl::ExportAll(
314 - self.convert_export_all_declaration(d),
315 - )),
316 - // The preserved TS module-interop statements are module
317 - // declarations in swc, which `convert_statement` (returning
318 - // `Stmt`) cannot represent, so they are rebuilt here.
319 - BabelStmt::Unknown(unknown) => match self.convert_unknown_to_module_decl(unknown) {
320 - Some(decl) => ModuleItem::ModuleDecl(decl),
321 - None => ModuleItem::Stmt(self.convert_statement(stmt)),
322 - },
323 - _ => ModuleItem::Stmt(self.convert_statement(stmt)),
324 - }
325 - }
326 -
327 - /// Rebuild the TS module-interop statements carried as raw
328 - /// [`BabelStmt::Unknown`] nodes. `None` (other node types, malformed raw
329 - /// JSON) routes the caller to the runtime-throw tripwire; silently
330 - /// dropping the statement is the failure mode this path exists to avoid.
331 - fn convert_unknown_to_module_decl(
332 - &self,
333 - unknown: &babel_stmt::UnknownStatement,
334 - ) -> Option<ModuleDecl> {
335 - let raw = unknown.raw().parse_value();
336 - let raw = &raw;
337 - match unknown.node_type() {
338 - "TSImportEqualsDeclaration" => {
339 - let id = self.ident_from_raw(raw.get("id")?)?;
340 - let module_ref = self.ts_module_ref_from_raw(raw.get("moduleReference")?)?;
341 - // Some Babel versions omit `importKind` for value imports,
342 - // so an absent key defaults to value.
343 - let is_type_only = match raw.get("importKind") {
344 - None => false,
345 - Some(kind) => match kind.as_str() {
346 - Some("type") => true,
347 - Some("value") => false,
348 - _ => return None,
349 - },
350 - };
351 - let is_export = match raw.get("isExport") {
352 - None => false,
353 - Some(value) => value.as_bool()?,
354 - };
355 - Some(ModuleDecl::TsImportEquals(Box::new(TsImportEqualsDecl {
356 - span: self.span(unknown.base()),
357 - is_export,
358 - is_type_only,
359 - id,
360 - module_ref,
361 - })))
362 - }
363 - "TSExportAssignment" => {
364 - let expr: BabelExpr =
365 - serde_json::from_value(raw.get("expression")?.clone()).ok()?;
366 - Some(ModuleDecl::TsExportAssignment(TsExportAssignment {
367 - span: self.span(unknown.base()),
368 - expr: Box::new(self.convert_expression(&expr)),
369 - }))
370 - }
371 - "TSNamespaceExportDeclaration" => {
372 - let id = self.ident_from_raw(raw.get("id")?)?;
373 - Some(ModuleDecl::TsNamespaceExport(TsNamespaceExportDecl {
374 - span: self.span(unknown.base()),
375 - id,
376 - }))
377 - }
378 - _ => None,
379 - }
380 - }
381 -
382 - fn ident_from_raw(&self, raw: &serde_json::Value) -> Option<Ident> {
383 - if raw.get("type").and_then(serde_json::Value::as_str) != Some("Identifier") {
384 - return None;
385 - }
386 - let id: babel_expr::Identifier = serde_json::from_value(raw.clone()).ok()?;
387 - Some(self.ident(&id.name, self.span_no_comments(&id.base)))
388 - }
389 -
390 - fn ts_module_ref_from_raw(&self, raw: &serde_json::Value) -> Option<TsModuleRef> {
391 - match raw.get("type").and_then(serde_json::Value::as_str)? {
392 - "TSExternalModuleReference" => {
393 - let expr = raw.get("expression")?;
394 - if expr.get("type").and_then(serde_json::Value::as_str) != Some("StringLiteral") {
395 - return None;
396 - }
397 - let lit: react_compiler_ast::literals::StringLiteral =
398 - serde_json::from_value(expr.clone()).ok()?;
399 - let ref_base: BaseNode = serde_json::from_value(raw.clone()).ok()?;
400 - Some(TsModuleRef::TsExternalModuleRef(TsExternalModuleRef {
401 - span: self.span_no_comments(&ref_base),
402 - expr: Str {
403 - span: self.span_no_comments(&lit.base),
404 - value: self.wtf8(&lit.value),
405 - raw: None,
406 - },
407 - }))
408 - }
409 - "TSQualifiedName" | "Identifier" => self
410 - .ts_entity_name_from_raw(raw)
411 - .map(TsModuleRef::TsEntityName),
412 - _ => None,
413 - }
414 - }
415 -
416 - fn ts_entity_name_from_raw(&self, raw: &serde_json::Value) -> Option<TsEntityName> {
417 - match raw.get("type").and_then(serde_json::Value::as_str)? {
418 - "Identifier" => self.ident_from_raw(raw).map(TsEntityName::Ident),
419 - "TSQualifiedName" => {
420 - let base: BaseNode = serde_json::from_value(raw.clone()).ok()?;
421 - let left = self.ts_entity_name_from_raw(raw.get("left")?)?;
422 - let right: babel_expr::Identifier =
423 - serde_json::from_value(raw.get("right")?.clone()).ok()?;
424 - Some(TsEntityName::TsQualifiedName(Box::new(TsQualifiedName {
425 - span: self.span_no_comments(&base),
426 - left,
427 - right: self.ident_name(&right.name, self.span_no_comments(&right.base)),
428 - })))
429 - }
430 - _ => None,
431 - }
432 - }
433 -
434 - // ===== Statements =====
435 -
436 - fn convert_statement(&self, stmt: &BabelStmt) -> Stmt {
437 - match stmt {
438 - BabelStmt::BlockStatement(s) => Stmt::Block(self.convert_block_statement(s)),
439 - BabelStmt::ReturnStatement(s) => Stmt::Return(ReturnStmt {
440 - span: self.span(&s.base),
441 - arg: s
442 - .argument
443 - .as_ref()
444 - .map(|a| Box::new(self.convert_expression(a))),
445 - }),
446 - BabelStmt::ExpressionStatement(s) => {
447 - let expr = self.convert_expression(&s.expression);
448 - // Wrap in parens if the expression starts with `{` (object pattern
449 - // in assignment) or `function` (IIFE), which would be ambiguous
450 - // with a block statement or function declaration.
451 - let needs_paren = match &expr {
452 - Expr::Assign(a) => {
453 - matches!(&a.left, AssignTarget::Pat(AssignTargetPat::Object(_)))
454 - }
455 - Expr::Call(c) => match &c.callee {
456 - Callee::Expr(e) => matches!(e.as_ref(), Expr::Fn(_)),
457 - _ => false,
458 - },
459 - _ => false,
460 - };
461 - let expr = if needs_paren {
462 - Expr::Paren(ParenExpr {
463 - span: self.span_no_comments(&s.base),
464 - expr: Box::new(expr),
465 - })
466 - } else {
467 - expr
468 - };
469 - Stmt::Expr(ExprStmt {
470 - span: self.span(&s.base),
471 - expr: Box::new(expr),
472 - })
473 - }
474 - BabelStmt::IfStatement(s) => Stmt::If(IfStmt {
475 - span: self.span(&s.base),
476 - test: Box::new(self.convert_expression(&s.test)),
477 - cons: Box::new(self.convert_statement(&s.consequent)),
478 - alt: s
479 - .alternate
480 - .as_ref()
481 - .map(|a| Box::new(self.convert_statement(a))),
482 - }),
483 - BabelStmt::ForStatement(s) => {
484 - let init = s.init.as_ref().map(|i| self.convert_for_init(i));
485 - let test = s
486 - .test
487 - .as_ref()
488 - .map(|t| Box::new(self.convert_expression(t)));
489 - let update = s
490 - .update
491 - .as_ref()
492 - .map(|u| Box::new(self.convert_expression(u)));
493 - let body = Box::new(self.convert_statement(&s.body));
494 - Stmt::For(ForStmt {
495 - span: self.span(&s.base),
496 - init,
497 - test,
498 - update,
499 - body,
500 - })
501 - }
502 - BabelStmt::WhileStatement(s) => Stmt::While(WhileStmt {
503 - span: self.span(&s.base),
504 - test: Box::new(self.convert_expression(&s.test)),
505 - body: Box::new(self.convert_statement(&s.body)),
506 - }),
507 - BabelStmt::DoWhileStatement(s) => Stmt::DoWhile(DoWhileStmt {
508 - span: self.span(&s.base),
509 - test: Box::new(self.convert_expression(&s.test)),
510 - body: Box::new(self.convert_statement(&s.body)),
511 - }),
512 - BabelStmt::ForInStatement(s) => Stmt::ForIn(ForInStmt {
513 - span: self.span(&s.base),
514 - left: self.convert_for_in_of_left(&s.left),
515 - right: Box::new(self.convert_expression(&s.right)),
516 - body: Box::new(self.convert_statement(&s.body)),
517 - }),
518 - BabelStmt::ForOfStatement(s) => Stmt::ForOf(ForOfStmt {
519 - span: self.span(&s.base),
520 - is_await: s.is_await,
521 - left: self.convert_for_in_of_left(&s.left),
522 - right: Box::new(self.convert_expression(&s.right)),
523 - body: Box::new(self.convert_statement(&s.body)),
524 - }),
525 - BabelStmt::SwitchStatement(s) => {
526 - let cases = s
527 - .cases
528 - .iter()
529 - .map(|c| SwitchCase {
530 - span: self.span(&c.base),
531 - test: c
532 - .test
533 - .as_ref()
534 - .map(|t| Box::new(self.convert_expression(t))),
535 - cons: c
536 - .consequent
537 - .iter()
538 - .map(|s| self.convert_statement(s))
539 - .collect(),
540 - })
541 - .collect();
542 - Stmt::Switch(SwitchStmt {
543 - span: self.span(&s.base),
544 - discriminant: Box::new(self.convert_expression(&s.discriminant)),
545 - cases,
546 - })
547 - }
548 - BabelStmt::ThrowStatement(s) => Stmt::Throw(ThrowStmt {
549 - span: self.span(&s.base),
550 - arg: Box::new(self.convert_expression(&s.argument)),
551 - }),
552 - BabelStmt::TryStatement(s) => {
553 - let block = self.convert_block_statement(&s.block);
554 - let handler = s.handler.as_ref().map(|h| self.convert_catch_clause(h));
555 - let finalizer = s
556 - .finalizer
557 - .as_ref()
558 - .map(|f| self.convert_block_statement(f));
559 - Stmt::Try(Box::new(TryStmt {
560 - span: self.span(&s.base),
561 - block,
562 - handler,
563 - finalizer,
564 - }))
565 - }
566 - BabelStmt::BreakStatement(s) => Stmt::Break(BreakStmt {
567 - span: self.span(&s.base),
568 - label: s.label.as_ref().map(|l| self.ident(&l.name, DUMMY_SP)),
569 - }),
570 - BabelStmt::ContinueStatement(s) => Stmt::Continue(ContinueStmt {
571 - span: self.span(&s.base),
572 - label: s.label.as_ref().map(|l| self.ident(&l.name, DUMMY_SP)),
573 - }),
574 - BabelStmt::LabeledStatement(s) => Stmt::Labeled(LabeledStmt {
575 - span: self.span(&s.base),
576 - label: self.ident(&s.label.name, DUMMY_SP),
577 - body: Box::new(self.convert_statement(&s.body)),
578 - }),
579 - BabelStmt::EmptyStatement(s) => Stmt::Empty(EmptyStmt {
580 - span: self.span(&s.base),
581 - }),
582 - BabelStmt::DebuggerStatement(s) => Stmt::Debugger(DebuggerStmt {
583 - span: self.span(&s.base),
584 - }),
585 - BabelStmt::WithStatement(s) => Stmt::With(WithStmt {
586 - span: self.span(&s.base),
587 - obj: Box::new(self.convert_expression(&s.object)),
588 - body: Box::new(self.convert_statement(&s.body)),
589 - }),
590 - BabelStmt::VariableDeclaration(d) => {
591 - Stmt::Decl(Decl::Var(Box::new(self.convert_variable_declaration(d))))
592 - }
593 - BabelStmt::FunctionDeclaration(f) => {
594 - Stmt::Decl(Decl::Fn(self.convert_function_declaration(f)))
595 - }
596 - BabelStmt::ClassDeclaration(c) => {
597 - let ident =
598 - c.id.as_ref()
599 - .map(|id| self.ident(&id.name, self.span(&id.base)))
600 - .unwrap_or_else(|| self.ident("_anonymous", DUMMY_SP));
601 - let super_class = c
602 - .super_class
603 - .as_ref()
604 - .map(|s| Box::new(self.convert_expression(s)));
605 - Stmt::Decl(Decl::Class(ClassDecl {
606 - ident,
607 - declare: c.declare.unwrap_or(false),
608 - class: Box::new(Class {
609 - span: self.span(&c.base),
610 - ctxt: SyntaxContext::empty(),
611 - decorators: vec![],
612 - body: vec![],
613 - super_class,
614 - is_abstract: false,
615 - type_params: None,
616 - super_type_params: None,
617 - implements: vec![],
618 - }),
619 - }))
620 - }
621 - // Import/export handled in convert_statement_to_module_item
622 - BabelStmt::ImportDeclaration(_)
623 - | BabelStmt::ExportNamedDeclaration(_)
624 - | BabelStmt::ExportDefaultDeclaration(_)
625 - | BabelStmt::ExportAllDeclaration(_) => Stmt::Empty(EmptyStmt { span: DUMMY_SP }),
626 - // TS declarations - extract from source text if available
627 - BabelStmt::TSTypeAliasDeclaration(d) => self
628 - .extract_source_stmt(&d.base)
629 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
630 - BabelStmt::TSInterfaceDeclaration(d) => self
631 - .extract_source_stmt(&d.base)
632 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
633 - BabelStmt::TSEnumDeclaration(d) => self
634 - .extract_source_stmt(&d.base)
635 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
636 - // Flow type declarations - extract from source text if available
637 - BabelStmt::TypeAlias(d) => self
638 - .extract_source_stmt(&d.base)
639 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
640 - BabelStmt::OpaqueType(d) => self
641 - .extract_source_stmt(&d.base)
642 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
643 - BabelStmt::InterfaceDeclaration(d) => self
644 - .extract_source_stmt(&d.base)
645 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
646 - BabelStmt::EnumDeclaration(d) => self
647 - .extract_source_stmt(&d.base)
648 - .unwrap_or(Stmt::Empty(EmptyStmt { span: DUMMY_SP })),
649 - // Other TS/Flow declarations
650 - BabelStmt::TSModuleDeclaration(_)
651 - | BabelStmt::TSDeclareFunction(_)
652 - | BabelStmt::DeclareVariable(_)
653 - | BabelStmt::DeclareFunction(_)
654 - | BabelStmt::DeclareClass(_)
655 - | BabelStmt::DeclareModule(_)
656 - | BabelStmt::DeclareModuleExports(_)
657 - | BabelStmt::DeclareExportDeclaration(_)
658 - | BabelStmt::DeclareExportAllDeclaration(_)
659 - | BabelStmt::DeclareInterface(_)
660 - | BabelStmt::DeclareTypeAlias(_)
661 - | BabelStmt::DeclareOpaqueType(_) => Stmt::Empty(EmptyStmt { span: DUMMY_SP }),
662 - // Only unknown node types without a reverse mapping reach this
663 - // arm. Degrading to EmptyStatement would silently drop the node,
664 - // so emit a deliberate runtime tripwire (a `throw` in generated
665 - // code) instead.
666 - BabelStmt::Unknown(unknown) => {
667 - let message = format!(
668 - "[react-compiler] internal error: unmodeled statement `{}` reached the SWC reverse converter",
669 - unknown.node_type()
670 - );
671 - Stmt::Throw(ThrowStmt {
672 - span: DUMMY_SP,
673 - arg: Box::new(Expr::Lit(Lit::Str(Str {
674 - span: DUMMY_SP,
675 - value: self.wtf8(&message),
676 - raw: None,
677 - }))),
678 - })
679 - }
680 - }
681 - }
682 -
683 - fn convert_block_statement(&self, block: &babel_stmt::BlockStatement) -> BlockStmt {
684 - let mut stmts: Vec<Stmt> = Vec::new();
685 -
686 - // Convert directives to expression statements at the beginning
687 - for dir in &block.directives {
688 - let span = self.span(&dir.base);
689 - let str_span = self.span(&dir.value.base);
690 - stmts.push(Stmt::Expr(ExprStmt {
691 - span,
692 - expr: Box::new(Expr::Lit(Lit::Str(Str {
693 - span: str_span,
694 - value: self.wtf8(&dir.value.value),
695 - raw: None,
696 - }))),
697 - }));
698 - }
699 -
700 - for s in &block.body {
701 - stmts.push(self.convert_statement(s));
702 - }
703 -
704 - BlockStmt {
705 - span: self.span(&block.base),
706 - ctxt: SyntaxContext::empty(),
707 - stmts,
708 - }
709 - }
710 -
711 - fn convert_catch_clause(&self, clause: &babel_stmt::CatchClause) -> CatchClause {
712 - let param = clause.param.as_ref().map(|p| self.convert_pattern(p));
713 - CatchClause {
714 - span: self.span(&clause.base),
715 - param,
716 - body: self.convert_block_statement(&clause.body),
717 - }
718 - }
719 -
720 - fn convert_for_init(&self, init: &babel_stmt::ForInit) -> VarDeclOrExpr {
721 - match init {
722 - babel_stmt::ForInit::VariableDeclaration(v) => {
723 - VarDeclOrExpr::VarDecl(Box::new(self.convert_variable_declaration(v)))
724 - }
725 - babel_stmt::ForInit::Expression(e) => {
726 - VarDeclOrExpr::Expr(Box::new(self.convert_expression(e)))
727 - }
728 - }
729 - }
730 -
731 - fn convert_for_in_of_left(&self, left: &babel_stmt::ForInOfLeft) -> ForHead {
732 - match left {
733 - babel_stmt::ForInOfLeft::VariableDeclaration(v) => {
734 - ForHead::VarDecl(Box::new(self.convert_variable_declaration(v)))
735 - }
736 - babel_stmt::ForInOfLeft::Pattern(p) => ForHead::Pat(Box::new(self.convert_pattern(p))),
737 - }
738 - }
739 -
740 - fn convert_variable_declaration(&self, decl: &babel_stmt::VariableDeclaration) -> VarDecl {
741 - let kind = match decl.kind {
742 - babel_stmt::VariableDeclarationKind::Var => VarDeclKind::Var,
743 - babel_stmt::VariableDeclarationKind::Let => VarDeclKind::Let,
744 - babel_stmt::VariableDeclarationKind::Const => VarDeclKind::Const,
745 - babel_stmt::VariableDeclarationKind::Using => VarDeclKind::Var, // SWC doesn't have Using
746 - };
747 - let decls = decl
748 - .declarations
749 - .iter()
750 - .map(|d| self.convert_variable_declarator(d))
751 - .collect();
752 - let declare = decl.declare.unwrap_or(false);
753 - VarDecl {
754 - span: self.span(&decl.base),
755 - ctxt: SyntaxContext::empty(),
756 - kind,
757 - declare,
758 - decls,
759 - }
760 - }
761 -
762 - fn convert_variable_declarator(&self, d: &babel_stmt::VariableDeclarator) -> VarDeclarator {
763 - let name = self.convert_pattern(&d.id);
764 - let init = d
765 - .init
766 - .as_ref()
767 - .map(|e| Box::new(self.convert_expression(e)));
768 - let definite = d.definite.unwrap_or(false);
769 - VarDeclarator {
770 - span: self.span(&d.base),
771 - name,
772 - init,
773 - definite,
774 - }
775 - }
776 -
777 - // ===== Expressions =====
778 -
779 - fn convert_expression(&self, expr: &BabelExpr) -> Expr {
780 - match expr {
781 - BabelExpr::Identifier(id) => {
782 - let span = self.span(&id.base);
783 - Expr::Ident(self.ident(&id.name, span))
784 - }
785 - BabelExpr::StringLiteral(lit) => Expr::Lit(Lit::Str(Str {
786 - span: self.span(&lit.base),
787 - value: self.wtf8(&lit.value),
788 - raw: self.escape_string_raw(&lit.value),
789 - })),
790 - BabelExpr::NumericLiteral(lit) => {
791 - // Convert -0.0 to 0.0 to match Babel's codegen behavior.
792 - // Babel outputs `0` for both `-0` and `0`.
793 - let value = if lit.value == 0.0 && lit.value.is_sign_negative() {
794 - 0.0
795 - } else {
796 - lit.value
797 - };
798 - Expr::Lit(Lit::Num(Number {
799 - span: self.span(&lit.base),
800 - value,
801 - raw: None,
802 - }))
803 - }
804 - BabelExpr::BooleanLiteral(lit) => Expr::Lit(Lit::Bool(Bool {
805 - span: self.span(&lit.base),
806 - value: lit.value,
807 - })),
808 - BabelExpr::NullLiteral(lit) => Expr::Lit(Lit::Null(Null {
809 - span: self.span(&lit.base),
810 - })),
811 - BabelExpr::BigIntLiteral(lit) => Expr::Lit(Lit::BigInt(BigInt {
812 - span: self.span(&lit.base),
813 - value: Box::new(lit.value.parse().unwrap_or_default()),
814 - raw: None,
815 - })),
816 - BabelExpr::RegExpLiteral(lit) => Expr::Lit(Lit::Regex(Regex {
817 - span: self.span(&lit.base),
818 - exp: self.atom(&lit.pattern),
819 - flags: self.atom(&lit.flags),
820 - })),
821 - BabelExpr::CallExpression(call) => {
822 - let callee = self.convert_expression(&call.callee);
823 - let args = self.convert_arguments(&call.arguments);
824 - // Wrap arrow/function expressions in parens when used as
825 - // call targets (IIFEs). SWC codegen does not add parens for
826 - // `(() => ...)()`, resulting in incorrect code.
827 - let callee = match &callee {
828 - Expr::Arrow(_) | Expr::Fn(_) => Expr::Paren(ParenExpr {
829 - span: callee.span(),
830 - expr: Box::new(callee),
831 - }),
832 - _ => callee,
833 - };
834 - Expr::Call(CallExpr {
835 - span: self.span(&call.base),
836 - ctxt: SyntaxContext::empty(),
837 - callee: Callee::Expr(Box::new(callee)),
838 - args,
839 - type_args: None,
840 - })
841 - }
842 - BabelExpr::MemberExpression(m) => self.convert_member_expression(m),
843 - BabelExpr::OptionalCallExpression(call) => {
844 - let callee = self.convert_expression_for_chain(&call.callee);
845 - let args = self.convert_arguments(&call.arguments);
846 - let base = OptChainBase::Call(OptCall {
847 - span: self.span(&call.base),
848 - ctxt: SyntaxContext::empty(),
849 - callee: Box::new(callee),
850 - args,
851 - type_args: None,
852 - });
853 - Expr::OptChain(OptChainExpr {
854 - span: self.span(&call.base),
855 - optional: call.optional,
856 - base: Box::new(base),
857 - })
858 - }
859 - BabelExpr::OptionalMemberExpression(m) => {
860 - let base = self.convert_optional_member_to_chain_base(m);
861 - Expr::OptChain(OptChainExpr {
862 - span: self.span(&m.base),
863 - optional: m.optional,
864 - base: Box::new(base),
865 - })
866 - }
867 - BabelExpr::BinaryExpression(bin) => {
868 - let op = self.convert_binary_operator(&bin.operator);
869 - Expr::Bin(BinExpr {
870 - span: self.span(&bin.base),
871 - op,
872 - left: Box::new(self.convert_expression(&bin.left)),
873 - right: Box::new(self.convert_expression(&bin.right)),
874 - })
875 - }
876 - BabelExpr::LogicalExpression(log) => {
877 - let op = self.convert_logical_operator(&log.operator);
878 - let span = self.span(&log.base);
879 - let bin = Expr::Bin(BinExpr {
880 - span,
881 - op,
882 - left: Box::new(self.convert_expression(&log.left)),
883 - right: Box::new(self.convert_expression(&log.right)),
884 - });
885 - // Wrap all logical expressions in parentheses. Logical
886 - // operators (||, &&, ??) have lower precedence than most
887 - // binary operators, but SWC's codegen does not always insert
888 - // parens correctly (e.g., `a + b || c` vs `a + (b || c)`).
889 - // Wrapping unconditionally is safe.
890 - Expr::Paren(ParenExpr {
891 - span,
892 - expr: Box::new(bin),
893 - })
894 - }
895 - BabelExpr::UnaryExpression(un) => {
896 - let op = self.convert_unary_operator(&un.operator);
897 - Expr::Unary(UnaryExpr {
898 - span: self.span(&un.base),
899 - op,
900 - arg: Box::new(self.convert_expression(&un.argument)),
901 - })
902 - }
903 - BabelExpr::UpdateExpression(up) => {
904 - let op = self.convert_update_operator(&up.operator);
905 - Expr::Update(UpdateExpr {
906 - span: self.span(&up.base),
907 - op,
908 - prefix: up.prefix,
909 - arg: Box::new(self.convert_expression(&up.argument)),
910 - })
911 - }
912 - BabelExpr::ConditionalExpression(cond) => {
913 - let span = self.span(&cond.base);
914 - // Wrap conditional expressions in parentheses. SWC's codegen
915 - // does not always insert parens for ternaries inside binary
916 - // or assignment expressions (e.g., `x + cond ? a : b` instead
917 - // of `x + (cond ? a : b)`).
918 - Expr::Paren(ParenExpr {
919 - span,
920 - expr: Box::new(Expr::Cond(CondExpr {
921 - span,
922 - test: Box::new(self.convert_expression(&cond.test)),
923 - cons: Box::new(self.convert_expression(&cond.consequent)),
924 - alt: Box::new(self.convert_expression(&cond.alternate)),
925 - })),
926 - })
927 - }
928 - BabelExpr::AssignmentExpression(assign) => {
929 - let op = self.convert_assignment_operator(&assign.operator);
930 - let left = self.convert_pattern_to_assign_target(&assign.left);
931 - let span = self.span(&assign.base);
932 - let assign_expr = Expr::Assign(AssignExpr {
933 - span,
934 - op,
935 - left,
936 - right: Box::new(self.convert_expression(&assign.right)),
937 - });
938 - // Wrap assignment expressions in parentheses. SWC's codegen
939 - // does not always insert necessary parens for assignments
940 - // when they appear as operands of binary/logical expressions
941 - // (e.g., `x + x = 2` instead of `x + (x = 2)`).
942 - Expr::Paren(ParenExpr {
943 - span,
944 - expr: Box::new(assign_expr),
945 - })
946 - }
947 - BabelExpr::SequenceExpression(seq) => {
948 - let exprs = seq
949 - .expressions
950 - .iter()
951 - .map(|e| Box::new(self.convert_expression(e)))
952 - .collect();
953 - let span = self.span(&seq.base);
954 - // Wrap sequence expressions in parentheses. SWC's codegen
955 - // does not always insert necessary parens for sequence
956 - // expressions (e.g., in ternary consequent position), so
957 - // wrapping unconditionally is safe and prevents parse errors.
958 - Expr::Paren(ParenExpr {
959 - span,
960 - expr: Box::new(Expr::Seq(SeqExpr { span, exprs })),
961 - })
962 - }
963 - BabelExpr::ArrowFunctionExpression(arrow) => self.convert_arrow_function(arrow),
964 - BabelExpr::FunctionExpression(func) => {
965 - let ident = func
966 - .id
967 - .as_ref()
968 - .map(|id| self.ident(&id.name, self.span(&id.base)));
969 - let params = self.convert_params(&func.params);
970 - let body = Some(self.convert_block_statement(&func.body));
971 - Expr::Fn(FnExpr {
972 - ident,
973 - function: Box::new(Function {
974 - params,
975 - decorators: vec![],
976 - span: self.span(&func.base),
977 - ctxt: SyntaxContext::empty(),
978 - body,
979 - is_generator: func.generator,
980 - is_async: func.is_async,
981 - type_params: None,
982 - return_type: None,
983 - }),
984 - })
985 - }
986 - BabelExpr::ObjectExpression(obj) => {
987 - let props = obj
988 - .properties
989 - .iter()
990 - .map(|p| self.convert_object_expression_property(p))
991 - .collect();
992 - Expr::Object(ObjectLit {
993 - span: self.span(&obj.base),
994 - props,
995 - })
996 - }
997 - BabelExpr::ArrayExpression(arr) => {
998 - let elems = arr
999 - .elements
1000 - .iter()
1001 - .map(|e| self.convert_array_element(e))
1002 - .collect();
1003 - Expr::Array(ArrayLit {
1004 - span: self.span(&arr.base),
1005 - elems,
1006 - })
1007 - }
1008 - BabelExpr::NewExpression(n) => {
1009 - let callee = Box::new(self.convert_expression(&n.callee));
1010 - let args = Some(self.convert_arguments(&n.arguments));
1011 - Expr::New(NewExpr {
1012 - span: self.span(&n.base),
1013 - ctxt: SyntaxContext::empty(),
1014 - callee,
1015 - args,
1016 - type_args: None,
1017 - })
1018 - }
1019 - BabelExpr::TemplateLiteral(tl) => {
1020 - let template = self.convert_template_literal(tl);
1021 - Expr::Tpl(template)
1022 - }
1023 - BabelExpr::TaggedTemplateExpression(tag) => {
1024 - let t = Box::new(self.convert_expression(&tag.tag));
1025 - let tpl = Box::new(self.convert_template_literal(&tag.quasi));
1026 - Expr::TaggedTpl(TaggedTpl {
1027 - span: self.span(&tag.base),
1028 - ctxt: SyntaxContext::empty(),
1029 - tag: t,
1030 - type_params: None,
1031 - tpl,
1032 - })
1033 - }
1034 - BabelExpr::AwaitExpression(a) => Expr::Await(AwaitExpr {
1035 - span: self.span(&a.base),
1036 - arg: Box::new(self.convert_expression(&a.argument)),
1037 - }),
1038 - BabelExpr::YieldExpression(y) => Expr::Yield(YieldExpr {
1039 - span: self.span(&y.base),
1040 - delegate: y.delegate,
1041 - arg: y
1042 - .argument
1043 - .as_ref()
1044 - .map(|a| Box::new(self.convert_expression(a))),
1045 - }),
1046 - BabelExpr::SpreadElement(s) => {
1047 - // SpreadElement can't be a standalone expression in SWC.
1048 - // Return the argument directly as a fallback.
1049 - self.convert_expression(&s.argument)
1050 - }
1051 - BabelExpr::MetaProperty(mp) => Expr::MetaProp(MetaPropExpr {
1052 - span: self.span(&mp.base),
1053 - kind: match (mp.meta.name.as_str(), mp.property.name.as_str()) {
1054 - ("new", "target") => MetaPropKind::NewTarget,
1055 - ("import", "meta") => MetaPropKind::ImportMeta,
1056 - _ => MetaPropKind::NewTarget,
1057 - },
1058 - }),
1059 - BabelExpr::ClassExpression(c) => {
1060 - let ident =
1061 - c.id.as_ref()
1062 - .map(|id| self.ident(&id.name, self.span(&id.base)));
1063 - let super_class = c
1064 - .super_class
1065 - .as_ref()
1066 - .map(|s| Box::new(self.convert_expression(s)));
1067 - Expr::Class(ClassExpr {
1068 - ident,
1069 - class: Box::new(Class {
1070 - span: self.span(&c.base),
1071 - ctxt: SyntaxContext::empty(),
1072 - decorators: vec![],
1073 - body: vec![],
1074 - super_class,
1075 - is_abstract: false,
1076 - type_params: None,
1077 - super_type_params: None,
1078 - implements: vec![],
1079 - }),
1080 - })
1081 - }
1082 - BabelExpr::PrivateName(p) => Expr::PrivateName(PrivateName {
1083 - span: self.span(&p.base),
1084 - name: self.atom(&p.id.name),
1085 - }),
1086 - BabelExpr::Super(s) => Expr::Ident(self.ident("super", self.span(&s.base))),
1087 - BabelExpr::Import(i) => Expr::Ident(self.ident("import", self.span(&i.base))),
1088 - BabelExpr::ThisExpression(t) => Expr::This(ThisExpr {
1089 - span: self.span(&t.base),
1090 - }),
1091 - BabelExpr::ParenthesizedExpression(p) => Expr::Paren(ParenExpr {
1092 - span: self.span(&p.base),
1093 - expr: Box::new(self.convert_expression(&p.expression)),
1094 - }),
1095 - BabelExpr::JSXElement(el) => {
1096 - let element = self.convert_jsx_element(el.as_ref());
1097 - Expr::JSXElement(Box::new(element))
1098 - }
1099 - BabelExpr::JSXFragment(frag) => {
1100 - let fragment = self.convert_jsx_fragment(frag);
1101 - Expr::JSXFragment(fragment)
1102 - }
1103 - // TS expressions - preserve as SWC TS nodes
1104 - BabelExpr::TSAsExpression(e) => {
1105 - let expr = Box::new(self.convert_expression(&e.expression));
1106 - let span = self.span(&e.base);
1107 - let annotation = e.type_annotation.parse_value();
1108 - // Check if this is "as const" — Babel represents it as
1109 - // TSAsExpression with typeAnnotation: TSTypeReference { typeName: Identifier { name: "const" } }
1110 - let is_as_const = annotation.get("type").and_then(|v| v.as_str())
1111 - == Some("TSTypeReference")
1112 - && annotation
1113 - .get("typeName")
1114 - .and_then(|tn| tn.get("name"))
1115 - .and_then(|n| n.as_str())
1116 - == Some("const");
1117 -
1118 - if is_as_const {
1119 - Expr::TsConstAssertion(TsConstAssertion { span, expr })
1120 - } else {
1121 - let type_ann = self.convert_ts_type_from_json(&annotation, span);
1122 - Expr::TsAs(TsAsExpr {
1123 - span,
1124 - expr,
1125 - type_ann: Box::new(type_ann),
1126 - })
1127 - }
1128 - }
1129 - BabelExpr::TSSatisfiesExpression(e) => self.convert_expression(&e.expression),
1130 - BabelExpr::TSNonNullExpression(e) => Expr::TsNonNull(TsNonNullExpr {
1131 - span: self.span(&e.base),
1132 - expr: Box::new(self.convert_expression(&e.expression)),
1133 - }),
1134 - BabelExpr::TSTypeAssertion(e) => self.convert_expression(&e.expression),
1135 - BabelExpr::TSInstantiationExpression(e) => self.convert_expression(&e.expression),
1136 - BabelExpr::TypeCastExpression(e) => self.convert_expression(&e.expression),
1137 - BabelExpr::AssignmentPattern(p) => {
1138 - let left = self.convert_pattern_to_assign_target(&p.left);
1139 - Expr::Assign(AssignExpr {
1140 - span: self.span(&p.base),
1141 - op: AssignOp::Assign,
1142 - left,
1143 - right: Box::new(self.convert_expression(&p.right)),
1144 - })
1145 - }
1146 - }
1147 - }
1148 -
1149 - /// Convert an expression that may be used inside a chain (optional chaining).
1150 - ///
1151 - /// In Babel, a chain like `a?.b.c()` is represented as nested
1152 - /// OptionalMemberExpression / OptionalCallExpression nodes. Each node
1153 - /// has an `optional` flag indicating whether it uses `?.` at that point.
1154 - ///
1155 - /// In SWC, each `?.` point is wrapped in an `OptChainExpr`. Nodes in
1156 - /// the chain that do NOT have `?.` are plain `MemberExpr` / `CallExpr`.
1157 - ///
1158 - /// So when `optional: true`, we still need to emit `OptChainExpr`.
1159 - /// When `optional: false`, we emit a plain expr (part of the parent chain).
1160 - fn convert_expression_for_chain(&self, expr: &BabelExpr) -> Expr {
1161 - match expr {
1162 - BabelExpr::OptionalMemberExpression(m) => {
1163 - if m.optional {
1164 - // This node uses `?.`, wrap in OptChainExpr
1165 - let base = self.convert_optional_member_to_chain_base(m);
1166 - Expr::OptChain(OptChainExpr {
1167 - span: self.span(&m.base),
1168 - optional: true,
1169 - base: Box::new(base),
1170 - })
1171 - } else {
1172 - // Part of a chain but no `?.` here — plain MemberExpr
1173 - self.convert_optional_member_to_member_expr(m)
1174 - }
1175 - }
1176 - BabelExpr::OptionalCallExpression(call) => {
1177 - let callee = self.convert_expression_for_chain(&call.callee);
1178 - let args = self.convert_arguments(&call.arguments);
1179 - if call.optional {
1180 - // This node uses `?.()`, wrap in OptChainExpr
1181 - let base = OptChainBase::Call(OptCall {
1182 - span: self.span(&call.base),
1183 - ctxt: SyntaxContext::empty(),
1184 - callee: Box::new(callee),
1185 - args,
1186 - type_args: None,
1187 - });
1188 - Expr::OptChain(OptChainExpr {
1189 - span: self.span(&call.base),
1190 - optional: true,
1191 - base: Box::new(base),
1192 - })
1193 - } else {
1194 - // Part of a chain but no `?.` here — plain CallExpr
1195 - Expr::Call(CallExpr {
1196 - span: self.span(&call.base),
1197 - ctxt: SyntaxContext::empty(),
1198 - callee: Callee::Expr(Box::new(callee)),
1199 - args,
1200 - type_args: None,
1201 - })
1202 - }
1203 - }
1204 - _ => self.convert_expression(expr),
1205 - }
1206 - }
1207 -
1208 - fn convert_member_expression(&self, m: &babel_expr::MemberExpression) -> Expr {
1209 - let object = self.convert_expression(&m.object);
1210 - // When an optional chain expression is used as the object of a
1211 - // non-optional member expression (e.g., `(props?.a).b`), wrap it
1212 - // in parens to properly terminate the optional chain. Without
1213 - // parens, SWC codegen emits `props?.a.b` which extends the chain.
1214 - let object = match &object {
1215 - Expr::OptChain(_) => Box::new(Expr::Paren(ParenExpr {
1216 - span: object.span(),
1217 - expr: Box::new(object),
1218 - })),
1219 - _ => Box::new(object),
1220 - };
1221 - if m.computed {
1222 - let property = self.convert_expression(&m.property);
1223 - Expr::Member(MemberExpr {
1224 - span: self.span(&m.base),
1225 - obj: object,
1226 - prop: MemberProp::Computed(ComputedPropName {
1227 - span: DUMMY_SP,
1228 - expr: Box::new(property),
1229 - }),
1230 - })
1231 - } else {
1232 - let prop_name = self.expression_to_ident_name(&m.property);
1233 - Expr::Member(MemberExpr {
1234 - span: self.span(&m.base),
1235 - obj: object,
1236 - prop: MemberProp::Ident(prop_name),
1237 - })
1238 - }
1239 - }
1240 -
1241 - fn convert_optional_member_to_chain_base(
1242 - &self,
1243 - m: &babel_expr::OptionalMemberExpression,
1244 - ) -> OptChainBase {
1245 - let object = Box::new(self.convert_expression_for_chain(&m.object));
1246 - if m.computed {
1247 - let property = self.convert_expression(&m.property);
1248 - OptChainBase::Member(MemberExpr {
1249 - span: self.span(&m.base),
1250 - obj: object,
1251 - prop: MemberProp::Computed(ComputedPropName {
1252 - span: DUMMY_SP,
1253 - expr: Box::new(property),
1254 - }),
1255 - })
1256 - } else {
1257 - let prop_name = self.expression_to_ident_name(&m.property);
1258 - OptChainBase::Member(MemberExpr {
1259 - span: self.span(&m.base),
1260 - obj: object,
1261 - prop: MemberProp::Ident(prop_name),
1262 - })
1263 - }
1264 - }
1265 -
1266 - fn convert_optional_member_to_member_expr(
1267 - &self,
1268 - m: &babel_expr::OptionalMemberExpression,
1269 - ) -> Expr {
1270 - let object = Box::new(self.convert_expression_for_chain(&m.object));
1271 - if m.computed {
1272 - let property = self.convert_expression(&m.property);
1273 - Expr::Member(MemberExpr {
1274 - span: self.span(&m.base),
1275 - obj: object,
1276 - prop: MemberProp::Computed(ComputedPropName {
1277 - span: DUMMY_SP,
1278 - expr: Box::new(property),
1279 - }),
1280 - })
1281 - } else {
1282 - let prop_name = self.expression_to_ident_name(&m.property);
1283 - Expr::Member(MemberExpr {
1284 - span: self.span(&m.base),
1285 - obj: object,
1286 - prop: MemberProp::Ident(prop_name),
1287 - })
1288 - }
1289 - }
1290 -
1291 - fn expression_to_ident_name(&self, expr: &BabelExpr) -> IdentName {
1292 - match expr {
1293 - BabelExpr::Identifier(id) => self.ident_name(&id.name, self.span(&id.base)),
1294 - _ => self.ident_name("__unknown__", DUMMY_SP),
1295 - }
1296 - }
1297 -
1298 - fn convert_arguments(&self, args: &[BabelExpr]) -> Vec<ExprOrSpread> {
1299 - args.iter().map(|a| self.convert_argument(a)).collect()
1300 - }
1301 -
1302 - fn convert_argument(&self, arg: &BabelExpr) -> ExprOrSpread {
1303 - match arg {
1304 - BabelExpr::SpreadElement(s) => ExprOrSpread {
1305 - spread: Some(self.span(&s.base)),
1306 - expr: Box::new(self.convert_expression(&s.argument)),
1307 - },
1308 - _ => ExprOrSpread {
1309 - spread: None,
1310 - expr: Box::new(self.convert_expression(arg)),
1311 - },
1312 - }
1313 - }
1314 -
1315 - fn convert_array_element(&self, elem: &Option<BabelExpr>) -> Option<ExprOrSpread> {
1316 - match elem {
1317 - None => None,
1318 - Some(BabelExpr::SpreadElement(s)) => Some(ExprOrSpread {
1319 - spread: Some(self.span(&s.base)),
1320 - expr: Box::new(self.convert_expression(&s.argument)),
1321 - }),
1322 - Some(e) => Some(ExprOrSpread {
1323 - spread: None,
1324 - expr: Box::new(self.convert_expression(e)),
1325 - }),
1326 - }
1327 - }
1328 -
1329 - fn convert_object_expression_property(
1330 - &self,
1331 - prop: &babel_expr::ObjectExpressionProperty,
1332 - ) -> PropOrSpread {
1333 - match prop {
1334 - babel_expr::ObjectExpressionProperty::ObjectProperty(p) => {
1335 - let key = if p.computed {
1336 - // Computed property key: [expr]
1337 - PropName::Computed(ComputedPropName {
1338 - span: DUMMY_SP,
1339 - expr: Box::new(self.convert_expression(&p.key)),
1340 - })
1341 - } else {
1342 - self.convert_expression_to_prop_name(&p.key)
1343 - };
1344 - let value = self.convert_expression(&p.value);
1345 - let method = p.method.unwrap_or(false);
1346 -
1347 - if p.shorthand {
1348 - PropOrSpread::Prop(Box::new(Prop::Shorthand(match &*p.key {
1349 - BabelExpr::Identifier(id) => self.ident(&id.name, self.span(&id.base)),
1350 - _ => self.ident("__unknown__", DUMMY_SP),
1351 - })))
1352 - } else if method {
1353 - // Method shorthand: { foo() {} }
1354 - // The value should be a function expression
1355 - let func = match value {
1356 - Expr::Fn(fn_expr) => *fn_expr.function,
1357 - _ => {
1358 - // Fallback: wrap in a key-value
1359 - return PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1360 - key,
1361 - value: Box::new(value),
1362 - })));
1363 - }
1364 - };
1365 - PropOrSpread::Prop(Box::new(Prop::Method(MethodProp {
1366 - key,
1367 - function: Box::new(func),
1368 - })))
1369 - } else {
1370 - PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1371 - key,
1372 - value: Box::new(value),
1373 - })))
1374 - }
1375 - }
1376 - babel_expr::ObjectExpressionProperty::ObjectMethod(m) => {
1377 - let key = if m.computed {
1378 - PropName::Computed(ComputedPropName {
1379 - span: DUMMY_SP,
1380 - expr: Box::new(self.convert_expression(&m.key)),
1381 - })
1382 - } else {
1383 - self.convert_expression_to_prop_name(&m.key)
1384 - };
1385 - let func = self.convert_object_method_to_function(m);
1386 - match m.kind {
1387 - babel_expr::ObjectMethodKind::Get => {
1388 - PropOrSpread::Prop(Box::new(Prop::Getter(GetterProp {
1389 - span: self.span(&m.base),
1390 - key,
1391 - type_ann: None,
1392 - body: func.body,
1393 - })))
1394 - }
1395 - babel_expr::ObjectMethodKind::Set => {
1396 - let param = func
1397 - .params
1398 - .into_iter()
1399 - .next()
1400 - .map(|p| Box::new(p.pat))
1401 - .unwrap_or_else(|| {
1402 - Box::new(Pat::Ident(self.binding_ident("_", DUMMY_SP)))
1403 - });
1404 - PropOrSpread::Prop(Box::new(Prop::Setter(SetterProp {
1405 - span: self.span(&m.base),
1406 - key,
1407 - this_param: None,
1408 - param,
1409 - body: func.body,
1410 - })))
1411 - }
1412 - babel_expr::ObjectMethodKind::Method => {
1413 - PropOrSpread::Prop(Box::new(Prop::Method(MethodProp {
1414 - key,
1415 - function: Box::new(func),
1416 - })))
1417 - }
1418 - }
1419 - }
1420 - babel_expr::ObjectExpressionProperty::SpreadElement(s) => {
1421 - PropOrSpread::Spread(SpreadElement {
1422 - dot3_token: self.span(&s.base),
1423 - expr: Box::new(self.convert_expression(&s.argument)),
1424 - })
1425 - }
1426 - }
1427 - }
1428 -
1429 - fn convert_expression_to_prop_name(&self, expr: &BabelExpr) -> PropName {
1430 - match expr {
1431 - BabelExpr::Identifier(id) => {
1432 - PropName::Ident(self.ident_name(&id.name, self.span(&id.base)))
1433 - }
1434 - BabelExpr::StringLiteral(s) => PropName::Str(Str {
1435 - span: self.span(&s.base),
1436 - value: self.wtf8(&s.value),
1437 - raw: None,
1438 - }),
1439 - BabelExpr::NumericLiteral(n) => PropName::Num(Number {
1440 - span: self.span(&n.base),
1441 - value: n.value,
1442 - raw: None,
1443 - }),
1444 - _ => PropName::Computed(ComputedPropName {
1445 - span: DUMMY_SP,
1446 - expr: Box::new(self.convert_expression(expr)),
1447 - }),
1448 - }
1449 - }
1450 -
1451 - fn convert_template_literal(&self, tl: &babel_expr::TemplateLiteral) -> Tpl {
1452 - let quasis = tl
1453 - .quasis
1454 - .iter()
1455 - .map(|q| {
1456 - let cooked = q.value.cooked.as_ref().map(|c| self.wtf8(c));
1457 - TplElement {
1458 - span: self.span(&q.base),
1459 - tail: q.tail,
1460 - cooked,
1461 - raw: self.atom(&q.value.raw),
1462 - }
1463 - })
1464 - .collect();
1465 - let exprs = tl
1466 - .expressions
1467 - .iter()
1468 - .map(|e| Box::new(self.convert_expression(e)))
1469 - .collect();
1470 - Tpl {
1471 - span: self.span(&tl.base),
1472 - exprs,
1473 - quasis,
1474 - }
1475 - }
1476 -
1477 - // ===== Functions =====
1478 -
1479 - fn convert_function_declaration(&self, f: &babel_stmt::FunctionDeclaration) -> FnDecl {
1480 - let ident =
1481 - f.id.as_ref()
1482 - .map(|id| self.ident(&id.name, self.span(&id.base)))
1483 - .unwrap_or_else(|| self.ident("_anonymous", DUMMY_SP));
1484 - let params = self.convert_params(&f.params);
1485 - let body = Some(self.convert_block_statement(&f.body));
1486 - let declare = f.declare.unwrap_or(false);
1487 - FnDecl {
1488 - ident,
1489 - declare,
1490 - function: Box::new(Function {
1491 - params,
1492 - decorators: vec![],
1493 - span: self.span(&f.base),
1494 - ctxt: SyntaxContext::empty(),
1495 - body,
1496 - is_generator: f.generator,
1497 - is_async: f.is_async,
1498 - type_params: None,
1499 - return_type: None,
1500 - }),
1501 - }
1502 - }
1503 -
1504 - fn convert_object_method_to_function(&self, m: &babel_expr::ObjectMethod) -> Function {
1505 - let params = self.convert_params(&m.params);
1506 - let body = Some(self.convert_block_statement(&m.body));
1507 - Function {
1508 - params,
1509 - decorators: vec![],
1510 - span: self.span(&m.base),
1511 - ctxt: SyntaxContext::empty(),
1512 - body,
1513 - is_generator: m.generator,
1514 - is_async: m.is_async,
1515 - type_params: None,
1516 - return_type: None,
1517 - }
1518 - }
1519 -
1520 - fn convert_arrow_function(&self, arrow: &babel_expr::ArrowFunctionExpression) -> Expr {
1521 - let is_expression = arrow.expression.unwrap_or(false);
1522 - let params = arrow
1523 - .params
1524 - .iter()
1525 - .map(|p| self.convert_pattern(p))
1526 - .collect();
1527 -
1528 - let body: Box<BlockStmtOrExpr> = match &*arrow.body {
1529 - babel_expr::ArrowFunctionBody::BlockStatement(block) => Box::new(
1530 - BlockStmtOrExpr::BlockStmt(self.convert_block_statement(block)),
1531 - ),
1532 - babel_expr::ArrowFunctionBody::Expression(expr) => {
1533 - if is_expression {
1534 - let converted = self.convert_expression(expr);
1535 - // Wrap object expressions in parens to prevent ambiguity
1536 - // with block bodies: `() => ({...})` vs `() => {...}`
1537 - let converted = if matches!(&converted, Expr::Object(_)) {
1538 - Expr::Paren(ParenExpr {
1539 - span: converted.span(),
1540 - expr: Box::new(converted),
1541 - })
1542 - } else {
1543 - converted
1544 - };
1545 - Box::new(BlockStmtOrExpr::Expr(Box::new(converted)))
1546 - } else {
1547 - // Wrap in block with return
1548 - let ret_stmt = Stmt::Return(ReturnStmt {
1549 - span: DUMMY_SP,
1550 - arg: Some(Box::new(self.convert_expression(expr))),
1551 - });
1552 - Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
1553 - span: DUMMY_SP,
1554 - ctxt: SyntaxContext::empty(),
1555 - stmts: vec![ret_stmt],
1556 - }))
1557 - }
1558 - }
1559 - };
1560 -
1561 - Expr::Arrow(ArrowExpr {
1562 - span: self.span(&arrow.base),
1563 - ctxt: SyntaxContext::empty(),
1564 - params,
1565 - body,
1566 - is_async: arrow.is_async,
1567 - is_generator: arrow.generator,
1568 - return_type: None,
1569 - type_params: None,
1570 - })
1571 - }
1572 -
1573 - fn convert_params(&self, params: &[PatternLike]) -> Vec<Param> {
1574 - params
1575 - .iter()
1576 - .map(|p| Param {
1577 - span: DUMMY_SP,
1578 - decorators: vec![],
1579 - pat: self.convert_pattern(p),
1580 - })
1581 - .collect()
1582 - }
1583 -
1584 - // ===== Patterns =====
1585 -
1586 - fn convert_pattern(&self, pattern: &PatternLike) -> Pat {
1587 - match pattern {
1588 - PatternLike::Identifier(id) => {
1589 - let mut bi = self.binding_ident(&id.name, self.span(&id.base));
1590 - bi.id.optional = id.optional.unwrap_or(false);
1591 - // Preserve type annotations if present
1592 - if let Some(ref type_ann) = id.type_annotation {
1593 - bi.type_ann =
1594 - self.convert_ts_type_annotation_from_json(&type_ann.parse_value());
1595 - }
1596 - Pat::Ident(bi)
1597 - }
1598 - PatternLike::ObjectPattern(obj) => {
1599 - let mut props: Vec<ObjectPatProp> = Vec::new();
1600 -
1601 - for prop in &obj.properties {
1602 - match prop {
1603 - ObjectPatternProperty::ObjectProperty(p) => {
1604 - if p.shorthand {
1605 - // Shorthand: { x } or { x = default }
1606 - let value = self.convert_pattern(&p.value);
1607 - match &*p.key {
1608 - BabelExpr::Identifier(id) => {
1609 - let key_ident =
1610 - self.binding_ident(&id.name, self.span(&id.base));
1611 - match value {
1612 - Pat::Assign(assign_pat) => {
1613 - props.push(ObjectPatProp::Assign(AssignPatProp {
1614 - span: self.span(&p.base),
1615 - key: key_ident,
1616 - value: Some(assign_pat.right),
1617 - }));
1618 - }
1619 - _ => {
1620 - props.push(ObjectPatProp::Assign(AssignPatProp {
1621 - span: self.span(&p.base),
1622 - key: key_ident,
1623 - value: None,
1624 - }));
1625 - }
1626 - }
1627 - }
1628 - _ => {
1629 - // Fallback to key-value
1630 - let key = self.convert_expression_to_prop_name(&p.key);
1631 - props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
1632 - key,
1633 - value: Box::new(value),
1634 - }));
1635 - }
1636 - }
1637 - } else {
1638 - let key = self.convert_expression_to_prop_name(&p.key);
1639 - let value = self.convert_pattern(&p.value);
1640 - props.push(ObjectPatProp::KeyValue(KeyValuePatProp {
1641 - key,
1642 - value: Box::new(value),
1643 - }));
1644 - }
1645 - }
1646 - ObjectPatternProperty::RestElement(r) => {
1647 - let arg = Box::new(self.convert_pattern(&r.argument));
1648 - props.push(ObjectPatProp::Rest(RestPat {
1649 - span: self.span(&r.base),
1650 - dot3_token: self.span(&r.base),
1651 - arg,
1652 - type_ann: None,
1653 - }));
1654 - }
1655 - }
1656 - }
1657 -
1658 - Pat::Object(ObjectPat {
1659 - span: self.span(&obj.base),
1660 - props,
1661 - optional: false,
1662 - type_ann: None,
1663 - })
1664 - }
1665 - PatternLike::ArrayPattern(arr) => {
1666 - let elems = arr
1667 - .elements
1668 - .iter()
1669 - .map(|e| e.as_ref().map(|p| self.convert_pattern(p)))
1670 - .collect();
1671 - Pat::Array(ArrayPat {
1672 - span: self.span(&arr.base),
1673 - elems,
1674 - optional: false,
1675 - type_ann: None,
1676 - })
1677 - }
1678 - PatternLike::AssignmentPattern(ap) => {
1679 - let left = Box::new(self.convert_pattern(&ap.left));
1680 - let right = Box::new(self.convert_expression(&ap.right));
1681 - Pat::Assign(AssignPat {
1682 - span: self.span(&ap.base),
1683 - left,
1684 - right,
1685 - })
1686 - }
1687 - PatternLike::RestElement(r) => {
1688 - let arg = Box::new(self.convert_pattern(&r.argument));
1689 - Pat::Rest(RestPat {
1690 - span: self.span(&r.base),
1691 - dot3_token: self.span(&r.base),
1692 - arg,
1693 - type_ann: None,
1694 - })
1695 - }
1696 - PatternLike::MemberExpression(m) => {
1697 - // MemberExpression in pattern position - convert to an expression pattern
1698 - Pat::Expr(Box::new(self.convert_member_expression(m)))
1699 - }
1700 - // TS wrappers in pattern position: strip the type wrapper, keep the
1701 - // inner expression (unreachable for unsupported targets; non-panicking).
1702 - PatternLike::TSAsExpression(e) => {
1703 - Pat::Expr(Box::new(self.convert_expression(&e.expression)))
1704 - }
1705 - PatternLike::TSSatisfiesExpression(e) => {
1706 - Pat::Expr(Box::new(self.convert_expression(&e.expression)))
1707 - }
1708 - PatternLike::TSNonNullExpression(e) => {
1709 - Pat::Expr(Box::new(self.convert_expression(&e.expression)))
1710 - }
1711 - PatternLike::TSTypeAssertion(e) => {
1712 - Pat::Expr(Box::new(self.convert_expression(&e.expression)))
1713 - }
1714 - PatternLike::TypeCastExpression(e) => {
1715 - Pat::Expr(Box::new(self.convert_expression(&e.expression)))
1716 - }
1717 - }
1718 - }
1719 -
1720 - // ===== Patterns → AssignmentTarget =====
1721 -
1722 - fn convert_pattern_to_assign_target(&self, pattern: &PatternLike) -> AssignTarget {
1723 - match pattern {
1724 - PatternLike::Identifier(id) => AssignTarget::Simple(SimpleAssignTarget::Ident(
1725 - self.binding_ident(&id.name, self.span(&id.base)),
1726 - )),
1727 - PatternLike::MemberExpression(m) => {
1728 - let expr = self.convert_member_expression(m);
1729 - match expr {
1730 - Expr::Member(member) => {
1731 - AssignTarget::Simple(SimpleAssignTarget::Member(member))
1732 - }
1733 - _ => AssignTarget::Simple(SimpleAssignTarget::Ident(
1734 - self.binding_ident("__unknown__", DUMMY_SP),
1735 - )),
1736 - }
1737 - }
1738 - PatternLike::ObjectPattern(_obj) => {
1739 - let pat = self.convert_pattern(pattern);
1740 - match pat {
1741 - Pat::Object(obj_pat) => AssignTarget::Pat(AssignTargetPat::Object(obj_pat)),
1742 - _ => AssignTarget::Simple(SimpleAssignTarget::Ident(
1743 - self.binding_ident("__unknown__", DUMMY_SP),
1744 - )),
1745 - }
1746 - }
1747 - PatternLike::ArrayPattern(_arr) => {
1748 - let pat = self.convert_pattern(pattern);
1749 - match pat {
1750 - Pat::Array(arr_pat) => AssignTarget::Pat(AssignTargetPat::Array(arr_pat)),
1751 - _ => AssignTarget::Simple(SimpleAssignTarget::Ident(
1752 - self.binding_ident("__unknown__", DUMMY_SP),
1753 - )),
1754 - }
1755 - }
1756 - PatternLike::AssignmentPattern(ap) => {
1757 - // For assignment LHS, use the left side
1758 - self.convert_pattern_to_assign_target(&ap.left)
1759 - }
1760 - PatternLike::RestElement(r) => self.convert_pattern_to_assign_target(&r.argument),
1761 - PatternLike::TSAsExpression(_)
1762 - | PatternLike::TSSatisfiesExpression(_)
1763 - | PatternLike::TSNonNullExpression(_)
1764 - | PatternLike::TSTypeAssertion(_)
1765 - | PatternLike::TypeCastExpression(_) => AssignTarget::Simple(
1766 - SimpleAssignTarget::Ident(self.binding_ident("__unknown__", DUMMY_SP)),
1767 - ),
1768 - }
1769 - }
1770 -
1771 - // ===== JSX =====
1772 -
1773 - fn convert_jsx_element(
1774 - &self,
1775 - el: &react_compiler_ast::jsx::JSXElement,
1776 - ) -> swc_ecma_ast::JSXElement {
1777 - let opening = self.convert_jsx_opening_element(&el.opening_element);
1778 - let children: Vec<swc_ecma_ast::JSXElementChild> = el
1779 - .children
1780 - .iter()
1781 - .map(|c| self.convert_jsx_child(c))
1782 - .collect();
1783 - let closing = el
1784 - .closing_element
1785 - .as_ref()
1786 - .map(|c| self.convert_jsx_closing_element(c));
1787 - swc_ecma_ast::JSXElement {
1788 - span: self.span(&el.base),
1789 - opening,
1790 - children,
1791 - closing,
1792 - }
1793 - }
1794 -
1795 - fn convert_jsx_opening_element(
1796 - &self,
1797 - el: &react_compiler_ast::jsx::JSXOpeningElement,
1798 - ) -> swc_ecma_ast::JSXOpeningElement {
1799 - let name = self.convert_jsx_element_name(&el.name);
1800 - let attrs = el
1801 - .attributes
1802 - .iter()
1803 - .map(|a| self.convert_jsx_attribute_item(a))
1804 - .collect();
1805 - swc_ecma_ast::JSXOpeningElement {
1806 - span: self.span(&el.base),
1807 - name,
1808 - attrs,
1809 - self_closing: el.self_closing,
1810 - type_args: None,
1811 - }
1812 - }
1813 -
1814 - fn convert_jsx_closing_element(
1815 - &self,
1816 - el: &react_compiler_ast::jsx::JSXClosingElement,
1817 - ) -> swc_ecma_ast::JSXClosingElement {
1818 - let name = self.convert_jsx_element_name(&el.name);
1819 - swc_ecma_ast::JSXClosingElement {
1820 - span: self.span(&el.base),
1821 - name,
1822 - }
1823 - }
1824 -
1825 - fn convert_jsx_element_name(
1826 - &self,
1827 - name: &react_compiler_ast::jsx::JSXElementName,
1828 - ) -> swc_ecma_ast::JSXElementName {
1829 - match name {
1830 - react_compiler_ast::jsx::JSXElementName::JSXIdentifier(id) => {
1831 - swc_ecma_ast::JSXElementName::Ident(self.ident(&id.name, self.span(&id.base)))
1832 - }
1833 - react_compiler_ast::jsx::JSXElementName::JSXMemberExpression(m) => {
1834 - let member = self.convert_jsx_member_expression(m);
1835 - swc_ecma_ast::JSXElementName::JSXMemberExpr(member)
1836 - }
1837 - react_compiler_ast::jsx::JSXElementName::JSXNamespacedName(ns) => {
1838 - let namespace = self.ident_name(&ns.namespace.name, self.span(&ns.namespace.base));
1839 - let name = self.ident_name(&ns.name.name, self.span(&ns.name.base));
1840 - swc_ecma_ast::JSXElementName::JSXNamespacedName(swc_ecma_ast::JSXNamespacedName {
1841 - span: DUMMY_SP,
1842 - ns: namespace,
1843 - name,
1844 - })
1845 - }
1846 - }
1847 - }
1848 -
1849 - fn convert_jsx_member_expression(
1850 - &self,
1851 - m: &react_compiler_ast::jsx::JSXMemberExpression,
1852 - ) -> swc_ecma_ast::JSXMemberExpr {
1853 - let obj = self.convert_jsx_member_expression_object(&m.object);
1854 - let prop = self.ident_name(&m.property.name, self.span(&m.property.base));
1855 - swc_ecma_ast::JSXMemberExpr {
1856 - span: DUMMY_SP,
1857 - obj,
1858 - prop,
1859 - }
1860 - }
1861 -
1862 - fn convert_jsx_member_expression_object(
1863 - &self,
1864 - obj: &react_compiler_ast::jsx::JSXMemberExprObject,
1865 - ) -> swc_ecma_ast::JSXObject {
1866 - match obj {
1867 - react_compiler_ast::jsx::JSXMemberExprObject::JSXIdentifier(id) => {
1868 - swc_ecma_ast::JSXObject::Ident(self.ident(&id.name, self.span(&id.base)))
1869 - }
1870 - react_compiler_ast::jsx::JSXMemberExprObject::JSXMemberExpression(m) => {
1871 - let member = self.convert_jsx_member_expression(m);
1872 - swc_ecma_ast::JSXObject::JSXMemberExpr(Box::new(member))
1873 - }
1874 - }
1875 - }
1876 -
1877 - fn convert_jsx_attribute_item(
1878 - &self,
1879 - item: &react_compiler_ast::jsx::JSXAttributeItem,
1880 - ) -> swc_ecma_ast::JSXAttrOrSpread {
1881 - match item {
1882 - react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(attr) => {
1883 - let name = self.convert_jsx_attribute_name(&attr.name);
1884 - let value = attr
1885 - .value
1886 - .as_ref()
1887 - .map(|v| self.convert_jsx_attribute_value(v));
1888 - swc_ecma_ast::JSXAttrOrSpread::JSXAttr(swc_ecma_ast::JSXAttr {
1889 - span: self.span(&attr.base),
1890 - name,
1891 - value,
1892 - })
1893 - }
1894 - react_compiler_ast::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => {
1895 - swc_ecma_ast::JSXAttrOrSpread::SpreadElement(SpreadElement {
1896 - dot3_token: self.span(&s.base),
1897 - expr: Box::new(self.convert_expression(&s.argument)),
1898 - })
1899 - }
1900 - }
1901 - }
1902 -
1903 - fn convert_jsx_attribute_name(
1904 - &self,
1905 - name: &react_compiler_ast::jsx::JSXAttributeName,
1906 - ) -> swc_ecma_ast::JSXAttrName {
1907 - match name {
1908 - react_compiler_ast::jsx::JSXAttributeName::JSXIdentifier(id) => {
1909 - swc_ecma_ast::JSXAttrName::Ident(self.ident_name(&id.name, self.span(&id.base)))
1910 - }
1911 - react_compiler_ast::jsx::JSXAttributeName::JSXNamespacedName(ns) => {
1912 - let namespace = self.ident_name(&ns.namespace.name, self.span(&ns.namespace.base));
1913 - let name = self.ident_name(&ns.name.name, self.span(&ns.name.base));
1914 - swc_ecma_ast::JSXAttrName::JSXNamespacedName(swc_ecma_ast::JSXNamespacedName {
1915 - span: DUMMY_SP,
1916 - ns: namespace,
1917 - name,
1918 - })
1919 - }
1920 - }
1921 - }
1922 -
1923 - fn convert_jsx_attribute_value(
1924 - &self,
1925 - value: &react_compiler_ast::jsx::JSXAttributeValue,
1926 - ) -> swc_ecma_ast::JSXAttrValue {
1927 - match value {
1928 - react_compiler_ast::jsx::JSXAttributeValue::StringLiteral(s) => {
1929 - // For JSX attributes, if the value contains double quotes,
1930 - // use single quotes to avoid escaping issues that prettier
1931 - // can't parse (e.g., name="\"user\" name").
1932 - let raw = if s.value.contains('"') {
1933 - Some(Atom::from(format!(
1934 - "'{}'",
1935 - s.value.replace('\\', "\\\\").replace('\'', "\\'")
1936 - )))
1937 - } else {
1938 - self.escape_string_raw(&s.value)
1939 - };
1940 - swc_ecma_ast::JSXAttrValue::Str(Str {
1941 - span: self.span(&s.base),
1942 - value: self.wtf8(&s.value),
1943 - raw,
1944 - })
1945 - }
1946 - react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(ec) => {
1947 - let expr = self.convert_jsx_expression_container_expr(&ec.expression);
1948 - swc_ecma_ast::JSXAttrValue::JSXExprContainer(swc_ecma_ast::JSXExprContainer {
1949 - span: self.span(&ec.base),
1950 - expr,
1951 - })
1952 - }
1953 - react_compiler_ast::jsx::JSXAttributeValue::JSXElement(el) => {
1954 - let element = self.convert_jsx_element(el.as_ref());
1955 - swc_ecma_ast::JSXAttrValue::JSXElement(Box::new(element))
1956 - }
1957 - react_compiler_ast::jsx::JSXAttributeValue::JSXFragment(frag) => {
1958 - let fragment = self.convert_jsx_fragment(frag);
1959 - swc_ecma_ast::JSXAttrValue::JSXFragment(fragment)
1960 - }
1961 - }
1962 - }
1963 -
1964 - fn convert_jsx_expression_container_expr(
1965 - &self,
1966 - expr: &react_compiler_ast::jsx::JSXExpressionContainerExpr,
1967 - ) -> swc_ecma_ast::JSXExpr {
1968 - match expr {
1969 - react_compiler_ast::jsx::JSXExpressionContainerExpr::JSXEmptyExpression(e) => {
1970 - swc_ecma_ast::JSXExpr::JSXEmptyExpr(swc_ecma_ast::JSXEmptyExpr {
1971 - span: self.span(&e.base),
1972 - })
1973 - }
1974 - react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) => {
1975 - swc_ecma_ast::JSXExpr::Expr(Box::new(self.convert_expression(e)))
1976 - }
1977 - }
1978 - }
1979 -
1980 - fn convert_jsx_child(
1981 - &self,
1982 - child: &react_compiler_ast::jsx::JSXChild,
1983 - ) -> swc_ecma_ast::JSXElementChild {
1984 - match child {
1985 - react_compiler_ast::jsx::JSXChild::JSXText(t) => {
1986 - swc_ecma_ast::JSXElementChild::JSXText(swc_ecma_ast::JSXText {
1987 - span: self.span(&t.base),
1988 - value: self.atom(&t.value),
1989 - raw: self.atom(&t.value),
1990 - })
1991 - }
1992 - react_compiler_ast::jsx::JSXChild::JSXElement(el) => {
1993 - let element = self.convert_jsx_element(el.as_ref());
1994 - swc_ecma_ast::JSXElementChild::JSXElement(Box::new(element))
1995 - }
1996 - react_compiler_ast::jsx::JSXChild::JSXFragment(frag) => {
1997 - let fragment = self.convert_jsx_fragment(frag);
1998 - swc_ecma_ast::JSXElementChild::JSXFragment(fragment)
1999 - }
2000 - react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(ec) => {
2001 - let expr = self.convert_jsx_expression_container_expr(&ec.expression);
2002 - swc_ecma_ast::JSXElementChild::JSXExprContainer(swc_ecma_ast::JSXExprContainer {
2003 - span: self.span(&ec.base),
2004 - expr,
2005 - })
2006 - }
2007 - react_compiler_ast::jsx::JSXChild::JSXSpreadChild(s) => {
2008 - swc_ecma_ast::JSXElementChild::JSXSpreadChild(swc_ecma_ast::JSXSpreadChild {
2009 - span: self.span(&s.base),
2010 - expr: Box::new(self.convert_expression(&s.expression)),
2011 - })
2012 - }
2013 - }
2014 - }
2015 -
2016 - fn convert_jsx_fragment(
2017 - &self,
2018 - frag: &react_compiler_ast::jsx::JSXFragment,
2019 - ) -> swc_ecma_ast::JSXFragment {
2020 - let children = frag
2021 - .children
2022 - .iter()
2023 - .map(|c| self.convert_jsx_child(c))
2024 - .collect();
2025 - swc_ecma_ast::JSXFragment {
2026 - span: self.span(&frag.base),
2027 - opening: swc_ecma_ast::JSXOpeningFragment {
2028 - span: self.span(&frag.opening_fragment.base),
2029 - },
2030 - children,
2031 - closing: swc_ecma_ast::JSXClosingFragment {
2032 - span: self.span(&frag.closing_fragment.base),
2033 - },
2034 - }
2035 - }
2036 -
2037 - // ===== Import/Export =====
2038 -
2039 - fn convert_import_declaration(&self, decl: &ImportDeclaration) -> swc_ecma_ast::ImportDecl {
2040 - let specifiers = decl
2041 - .specifiers
2042 - .iter()
2043 - .map(|s| self.convert_import_specifier(s))
2044 - .collect();
2045 - let src = Box::new(Str {
2046 - span: self.span(&decl.source.base),
2047 - value: self.wtf8(&decl.source.value),
2048 - raw: None,
2049 - });
2050 - let type_only = matches!(decl.import_kind.as_ref(), Some(ImportKind::Type));
2051 - swc_ecma_ast::ImportDecl {
2052 - span: self.span(&decl.base),
2053 - specifiers,
2054 - src,
2055 - type_only,
2056 - with: None,
2057 - phase: Default::default(),
2058 - }
2059 - }
2060 -
2061 - fn convert_import_specifier(
2062 - &self,
2063 - spec: &react_compiler_ast::declarations::ImportSpecifier,
2064 - ) -> swc_ecma_ast::ImportSpecifier {
2065 - match spec {
2066 - react_compiler_ast::declarations::ImportSpecifier::ImportSpecifier(s) => {
2067 - let local = self.ident(&s.local.name, self.span(&s.local.base));
2068 - // Only set `imported` if it differs from `local` — otherwise
2069 - // SWC emits `foo as foo` instead of just `foo`.
2070 - let imported_name = match &s.imported {
2071 - react_compiler_ast::declarations::ModuleExportName::Identifier(id) => {
2072 - Some(&id.name)
2073 - }
2074 - react_compiler_ast::declarations::ModuleExportName::StringLiteral(_) => None,
2075 - };
2076 - let imported = if imported_name == Some(&s.local.name) {
2077 - None
2078 - } else {
2079 - Some(self.convert_module_export_name(&s.imported))
2080 - };
2081 - let is_type_only = matches!(s.import_kind.as_ref(), Some(ImportKind::Type));
2082 - swc_ecma_ast::ImportSpecifier::Named(ImportNamedSpecifier {
2083 - span: self.span(&s.base),
2084 - local,
2085 - imported,
2086 - is_type_only,
2087 - })
2088 - }
2089 - react_compiler_ast::declarations::ImportSpecifier::ImportDefaultSpecifier(s) => {
2090 - let local = self.ident(&s.local.name, self.span(&s.local.base));
2091 - swc_ecma_ast::ImportSpecifier::Default(ImportDefaultSpecifier {
2092 - span: self.span(&s.base),
2093 - local,
2094 - })
2095 - }
2096 - react_compiler_ast::declarations::ImportSpecifier::ImportNamespaceSpecifier(s) => {
2097 - let local = self.ident(&s.local.name, self.span(&s.local.base));
2098 - swc_ecma_ast::ImportSpecifier::Namespace(ImportStarAsSpecifier {
2099 - span: self.span(&s.base),
2100 - local,
2101 - })
2102 - }
2103 - }
2104 - }
2105 -
2106 - fn convert_module_export_name(
2107 - &self,
2108 - name: &react_compiler_ast::declarations::ModuleExportName,
2109 - ) -> swc_ecma_ast::ModuleExportName {
2110 - match name {
2111 - react_compiler_ast::declarations::ModuleExportName::Identifier(id) => {
2112 - swc_ecma_ast::ModuleExportName::Ident(self.ident(&id.name, self.span(&id.base)))
2113 - }
2114 - react_compiler_ast::declarations::ModuleExportName::StringLiteral(s) => {
2115 - swc_ecma_ast::ModuleExportName::Str(Str {
2116 - span: self.span(&s.base),
2117 - value: self.wtf8(&s.value),
2118 - raw: None,
2119 - })
2120 - }
2121 - }
2122 - }
2123 -
2124 - fn convert_export_named_to_module_item(&self, decl: &ExportNamedDeclaration) -> ModuleItem {
2125 - // If there's a declaration, emit as ExportDecl
2126 - if let Some(declaration) = &decl.declaration {
2127 - let swc_decl = self.convert_declaration(declaration);
2128 - return ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
2129 - span: self.span(&decl.base),
2130 - decl: swc_decl,
2131 - }));
2132 - }
2133 - self.convert_export_named_specifiers(decl)
2134 - }
2135 -
2136 - fn convert_declaration(&self, decl: &react_compiler_ast::declarations::Declaration) -> Decl {
2137 - match decl {
2138 - react_compiler_ast::declarations::Declaration::FunctionDeclaration(f) => {
2139 - Decl::Fn(self.convert_function_declaration(f))
2140 - }
2141 - react_compiler_ast::declarations::Declaration::VariableDeclaration(v) => {
2142 - Decl::Var(Box::new(self.convert_variable_declaration(v)))
2143 - }
2144 - react_compiler_ast::declarations::Declaration::ClassDeclaration(c) => {
2145 - let ident =
2146 - c.id.as_ref()
2147 - .map(|id| self.ident(&id.name, self.span(&id.base)))
2148 - .unwrap_or_else(|| self.ident("_anonymous", DUMMY_SP));
2149 - let super_class = c
2150 - .super_class
2151 - .as_ref()
2152 - .map(|s| Box::new(self.convert_expression(s)));
2153 - Decl::Class(ClassDecl {
2154 - ident,
2155 - declare: c.declare.unwrap_or(false),
2156 - class: Box::new(Class {
2157 - span: self.span(&c.base),
2158 - ctxt: SyntaxContext::empty(),
2159 - decorators: vec![],
2160 - body: vec![],
2161 - super_class,
2162 - is_abstract: false,
2163 - type_params: None,
2164 - super_type_params: None,
2165 - implements: vec![],
2166 - }),
2167 - })
2168 - }
2169 - _ => Decl::Var(Box::new(VarDecl {
2170 - span: DUMMY_SP,
2171 - ctxt: SyntaxContext::empty(),
2172 - kind: VarDeclKind::Const,
2173 - declare: true,
2174 - decls: vec![],
2175 - })),
2176 - }
2177 - }
2178 -
2179 - fn convert_export_named_specifiers(&self, decl: &ExportNamedDeclaration) -> ModuleItem {
2180 - let specifiers = decl
2181 - .specifiers
2182 - .iter()
2183 - .map(|s| self.convert_export_specifier(s))
2184 - .collect();
2185 - let src = decl.source.as_ref().map(|s| {
2186 - Box::new(Str {
2187 - span: self.span(&s.base),
2188 - value: self.wtf8(&s.value),
2189 - raw: None,
2190 - })
2191 - });
2192 - let type_only = matches!(decl.export_kind.as_ref(), Some(ExportKind::Type));
2193 -
2194 - ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
2195 - span: self.span(&decl.base),
2196 - specifiers,
2197 - src,
2198 - type_only,
2199 - with: None,
2200 - }))
2201 - }
2202 -
2203 - fn convert_export_specifier(
2204 - &self,
2205 - spec: &react_compiler_ast::declarations::ExportSpecifier,
2206 - ) -> swc_ecma_ast::ExportSpecifier {
2207 - match spec {
2208 - react_compiler_ast::declarations::ExportSpecifier::ExportSpecifier(s) => {
2209 - let orig = self.convert_module_export_name(&s.local);
2210 - // Only set `exported` if it differs from `local`
2211 - let local_name = match &s.local {
2212 - react_compiler_ast::declarations::ModuleExportName::Identifier(id) => {
2213 - Some(&id.name)
2214 - }
2215 - _ => None,
2216 - };
2217 - let exported_name = match &s.exported {
2218 - react_compiler_ast::declarations::ModuleExportName::Identifier(id) => {
2219 - Some(&id.name)
2220 - }
2221 - _ => None,
2222 - };
2223 - let exported = if local_name.is_some() && local_name == exported_name {
2224 - None
2225 - } else {
2226 - Some(self.convert_module_export_name(&s.exported))
2227 - };
2228 - let is_type_only = matches!(s.export_kind.as_ref(), Some(ExportKind::Type));
2229 - swc_ecma_ast::ExportSpecifier::Named(ExportNamedSpecifier {
2230 - span: self.span(&s.base),
2231 - orig,
2232 - exported,
2233 - is_type_only,
2234 - })
2235 - }
2236 - react_compiler_ast::declarations::ExportSpecifier::ExportDefaultSpecifier(s) => {
2237 - swc_ecma_ast::ExportSpecifier::Default(swc_ecma_ast::ExportDefaultSpecifier {
2238 - exported: self.ident(&s.exported.name, self.span(&s.exported.base)),
2239 - })
2240 - }
2241 - react_compiler_ast::declarations::ExportSpecifier::ExportNamespaceSpecifier(s) => {
2242 - let name = self.convert_module_export_name(&s.exported);
2243 - swc_ecma_ast::ExportSpecifier::Namespace(ExportNamespaceSpecifier {
2244 - span: self.span(&s.base),
2245 - name,
2246 - })
2247 - }
2248 - }
2249 - }
2250 -
2251 - fn convert_export_default_to_module_item(&self, decl: &ExportDefaultDeclaration) -> ModuleItem {
2252 - let span = self.span(&decl.base);
2253 - match &*decl.declaration {
2254 - BabelExportDefaultDecl::FunctionDeclaration(f) => {
2255 - let fd = self.convert_function_declaration(f);
2256 - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(
2257 - swc_ecma_ast::ExportDefaultDecl {
2258 - span,
2259 - decl: swc_ecma_ast::DefaultDecl::Fn(FnExpr {
2260 - ident: Some(fd.ident),
2261 - function: fd.function,
2262 - }),
2263 - },
2264 - ))
2265 - }
2266 - BabelExportDefaultDecl::ClassDeclaration(c) => {
2267 - let ident =
2268 - c.id.as_ref()
2269 - .map(|id| self.ident(&id.name, self.span(&id.base)));
2270 - let super_class = c
2271 - .super_class
2272 - .as_ref()
2273 - .map(|s| Box::new(self.convert_expression(s)));
2274 - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(
2275 - swc_ecma_ast::ExportDefaultDecl {
2276 - span,
2277 - decl: swc_ecma_ast::DefaultDecl::Class(ClassExpr {
2278 - ident,
2279 - class: Box::new(Class {
2280 - span,
2281 - ctxt: SyntaxContext::empty(),
2282 - decorators: vec![],
2283 - body: vec![],
2284 - super_class,
2285 - is_abstract: false,
2286 - type_params: None,
2287 - super_type_params: None,
2288 - implements: vec![],
2289 - }),
2290 - }),
2291 - },
2292 - ))
2293 - }
2294 - BabelExportDefaultDecl::EnumDeclaration(_) => {
2295 - // Flow enum declarations cannot be represented in SWC AST;
2296 - // emit a null placeholder to preserve the export shape.
2297 - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
2298 - span,
2299 - expr: Box::new(swc_ecma_ast::Expr::Lit(swc_ecma_ast::Lit::Null(
2300 - swc_ecma_ast::Null { span },
2301 - ))),
2302 - }))
2303 - }
2304 - BabelExportDefaultDecl::Expression(e) => {
2305 - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
2306 - span,
2307 - expr: Box::new(self.convert_expression(e)),
2308 - }))
2309 - }
2310 - }
2311 - }
2312 -
2313 - fn convert_export_all_declaration(
2314 - &self,
2315 - decl: &ExportAllDeclaration,
2316 - ) -> swc_ecma_ast::ExportAll {
2317 - let src = Box::new(Str {
2318 - span: self.span(&decl.source.base),
2319 - value: self.wtf8(&decl.source.value),
2320 - raw: None,
2321 - });
2322 - let type_only = matches!(decl.export_kind.as_ref(), Some(ExportKind::Type));
2323 - swc_ecma_ast::ExportAll {
2324 - span: self.span(&decl.base),
2325 - src,
2326 - type_only,
2327 - with: None,
2328 - }
2329 - }
2330 -
2331 - // ===== TS type helpers =====
2332 -
2333 - /// Convert a Babel TSTypeAnnotation JSON to an SWC TsTypeAnnotation.
2334 - /// Returns None if the JSON is not a valid type annotation.
2335 - fn convert_ts_type_annotation_from_json(
2336 - &self,
2337 - json: &serde_json::Value,
2338 - ) -> Option<Box<TsTypeAnn>> {
2339 - let type_name = json.get("type")?.as_str()?;
2340 - if type_name != "TSTypeAnnotation" && type_name != "TypeAnnotation" {
2341 - return None;
2342 - }
2343 - let type_annotation = json.get("typeAnnotation")?;
2344 - let ts_type = self.convert_ts_type_from_json(type_annotation, DUMMY_SP);
2345 - Some(Box::new(TsTypeAnn {
2346 - span: DUMMY_SP,
2347 - type_ann: Box::new(ts_type),
2348 - }))
2349 - }
2350 -
2351 - /// Convert a JSON-serialized TypeScript type annotation to an SWC TsType.
2352 - /// This handles common cases from the compiler's output. For unrecognized
2353 - /// types, it falls back to `any`.
2354 - fn convert_ts_type_from_json(&self, json: &serde_json::Value, span: Span) -> TsType {
2355 - let type_name = json.get("type").and_then(|v| v.as_str()).unwrap_or("");
2356 - match type_name {
2357 - "TSTypeReference" => {
2358 - let name = json
2359 - .get("typeName")
2360 - .and_then(|tn| tn.get("name"))
2361 - .and_then(|n| n.as_str())
2362 - .unwrap_or("unknown");
2363 - if name == "const" {
2364 - TsType::TsTypeRef(TsTypeRef {
2365 - span,
2366 - type_name: TsEntityName::Ident(self.ident("const", span)),
2367 - type_params: None,
2368 - })
2369 - } else {
2370 - TsType::TsTypeRef(TsTypeRef {
2371 - span,
2372 - type_name: TsEntityName::Ident(self.ident(name, span)),
2373 - type_params: None,
2374 - })
2375 - }
2376 - }
2377 - "TSNumberKeyword" => TsType::TsKeywordType(TsKeywordType {
2378 - span,
2379 - kind: TsKeywordTypeKind::TsNumberKeyword,
2380 - }),
2381 - "TSStringKeyword" => TsType::TsKeywordType(TsKeywordType {
2382 - span,
2383 - kind: TsKeywordTypeKind::TsStringKeyword,
2384 - }),
2385 - "TSBooleanKeyword" => TsType::TsKeywordType(TsKeywordType {
2386 - span,
2387 - kind: TsKeywordTypeKind::TsBooleanKeyword,
2388 - }),
2389 - "TSVoidKeyword" => TsType::TsKeywordType(TsKeywordType {
2390 - span,
2391 - kind: TsKeywordTypeKind::TsVoidKeyword,
2392 - }),
2393 - "TSNullKeyword" => TsType::TsKeywordType(TsKeywordType {
2394 - span,
2395 - kind: TsKeywordTypeKind::TsNullKeyword,
2396 - }),
2397 - "TSUndefinedKeyword" => TsType::TsKeywordType(TsKeywordType {
2398 - span,
2399 - kind: TsKeywordTypeKind::TsUndefinedKeyword,
2400 - }),
2401 - "TSAnyKeyword" => TsType::TsKeywordType(TsKeywordType {
2402 - span,
2403 - kind: TsKeywordTypeKind::TsAnyKeyword,
2404 - }),
2405 - "TSNeverKeyword" => TsType::TsKeywordType(TsKeywordType {
2406 - span,
2407 - kind: TsKeywordTypeKind::TsNeverKeyword,
2408 - }),
2409 - "TSUnionType" => {
2410 - let types = json
2411 - .get("types")
2412 - .and_then(|t| t.as_array())
2413 - .map(|arr| {
2414 - arr.iter()
2415 - .map(|t| Box::new(self.convert_ts_type_from_json(t, span)))
2416 - .collect::<Vec<_>>()
2417 - })
2418 - .unwrap_or_default();
2419 - TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsUnionType(
2420 - TsUnionType { span, types },
2421 - ))
2422 - }
2423 - "TSIntersectionType" => {
2424 - let types = json
2425 - .get("types")
2426 - .and_then(|t| t.as_array())
2427 - .map(|arr| {
2428 - arr.iter()
2429 - .map(|t| Box::new(self.convert_ts_type_from_json(t, span)))
2430 - .collect::<Vec<_>>()
2431 - })
2432 - .unwrap_or_default();
2433 - TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsIntersectionType(
2434 - TsIntersectionType { span, types },
2435 - ))
2436 - }
2437 - "TSLiteralType" => {
2438 - if let Some(literal) = json.get("literal") {
2439 - let lit_type = literal.get("type").and_then(|t| t.as_str()).unwrap_or("");
2440 - match lit_type {
2441 - "StringLiteral" => {
2442 - let value = literal.get("value").and_then(|v| v.as_str()).unwrap_or("");
2443 - TsType::TsLitType(TsLitType {
2444 - span,
2445 - lit: TsLit::Str(Str {
2446 - span,
2447 - value: self.wtf8(value),
2448 - raw: None,
2449 - }),
2450 - })
2451 - }
2452 - "NumericLiteral" => {
2453 - let value =
2454 - literal.get("value").and_then(|v| v.as_f64()).unwrap_or(0.0);
2455 - TsType::TsLitType(TsLitType {
2456 - span,
2457 - lit: TsLit::Number(Number {
2458 - span,
2459 - value,
2460 - raw: None,
2461 - }),
2462 - })
2463 - }
2464 - "BooleanLiteral" => {
2465 - let value = literal
2466 - .get("value")
2467 - .and_then(|v| v.as_bool())
2468 - .unwrap_or(false);
2469 - TsType::TsLitType(TsLitType {
2470 - span,
2471 - lit: TsLit::Bool(Bool { span, value }),
2472 - })
2473 - }
2474 - _ => TsType::TsKeywordType(TsKeywordType {
2475 - span,
2476 - kind: TsKeywordTypeKind::TsAnyKeyword,
2477 - }),
2478 - }
2479 - } else {
2480 - TsType::TsKeywordType(TsKeywordType {
2481 - span,
2482 - kind: TsKeywordTypeKind::TsAnyKeyword,
2483 - })
2484 - }
2485 - }
2486 - "TSArrayType" => {
2487 - let elem = json
2488 - .get("elementType")
2489 - .map(|t| self.convert_ts_type_from_json(t, span))
2490 - .unwrap_or(TsType::TsKeywordType(TsKeywordType {
2491 - span,
2492 - kind: TsKeywordTypeKind::TsAnyKeyword,
2493 - }));
2494 - TsType::TsArrayType(TsArrayType {
2495 - span,
2496 - elem_type: Box::new(elem),
2497 - })
2498 - }
2499 - "TSFunctionType"
2500 - | "TSTypeLiteral"
2501 - | "TSParenthesizedType"
2502 - | "TSTupleType"
2503 - | "TSOptionalType"
2504 - | "TSRestType"
2505 - | "TSConditionalType"
2506 - | "TSInferType"
2507 - | "TSMappedType"
2508 - | "TSIndexedAccessType"
2509 - | "TSTypeOperator"
2510 - | "TSTypePredicate"
2511 - | "TSImportType"
2512 - | "TSQualifiedName" => {
2513 - // For complex types, try to extract from source text
2514 - if let (Some(source), Some(start), Some(end)) = (
2515 - self.source_text.as_deref(),
2516 - json.get("start").and_then(|v| v.as_u64()),
2517 - json.get("end").and_then(|v| v.as_u64()),
2518 - ) {
2519 - let start_idx = (start as usize).saturating_sub(1);
2520 - let end_idx = (end as usize).saturating_sub(1);
2521 - if start_idx < source.len() && end_idx <= source.len() && start_idx < end_idx {
2522 - let text = &source[start_idx..end_idx];
2523 - // Parse the type using SWC
2524 - let wrapper = format!("type __T = {};", text);
2525 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
2526 - let fm = cm.new_source_file(
2527 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
2528 - wrapper,
2529 - );
2530 - let mut errors = vec![];
2531 - if let Ok(module) = swc_ecma_parser::parse_file_as_module(
2532 - &fm,
2533 - swc_ecma_parser::Syntax::Typescript(swc_ecma_parser::TsSyntax {
2534 - tsx: true,
2535 - ..Default::default()
2536 - }),
2537 - swc_ecma_ast::EsVersion::latest(),
2538 - None,
2539 - &mut errors,
2540 - ) {
2541 - if let Some(ModuleItem::Stmt(Stmt::Decl(Decl::TsTypeAlias(alias)))) =
2542 - module.body.into_iter().next()
2543 - {
2544 - return *alias.type_ann;
2545 - }
2546 - }
2547 - }
2548 - }
2549 - // Fallback
2550 - TsType::TsKeywordType(TsKeywordType {
2551 - span,
2552 - kind: TsKeywordTypeKind::TsAnyKeyword,
2553 - })
2554 - }
2555 - // Flow types
2556 - "NumberTypeAnnotation"
2557 - | "StringTypeAnnotation"
2558 - | "BooleanTypeAnnotation"
2559 - | "VoidTypeAnnotation"
2560 - | "NullLiteralTypeAnnotation"
2561 - | "AnyTypeAnnotation"
2562 - | "GenericTypeAnnotation"
2563 - | "UnionTypeAnnotation"
2564 - | "IntersectionTypeAnnotation"
2565 - | "NullableTypeAnnotation"
2566 - | "FunctionTypeAnnotation"
2567 - | "ObjectTypeAnnotation"
2568 - | "ArrayTypeAnnotation"
2569 - | "TupleTypeAnnotation"
2570 - | "TypeofTypeAnnotation"
2571 - | "NumberLiteralTypeAnnotation"
2572 - | "StringLiteralTypeAnnotation"
2573 - | "BooleanLiteralTypeAnnotation" => {
2574 - // For Flow types, try to extract from source text
2575 - if let (Some(source), Some(start), Some(end)) = (
2576 - self.source_text.as_deref(),
2577 - json.get("start").and_then(|v| v.as_u64()),
2578 - json.get("end").and_then(|v| v.as_u64()),
2579 - ) {
2580 - let start_idx = (start as usize).saturating_sub(1);
2581 - let end_idx = (end as usize).saturating_sub(1);
2582 - if start_idx < source.len() && end_idx <= source.len() && start_idx < end_idx {
2583 - let text = &source[start_idx..end_idx];
2584 - // For Flow types, we can use TS parser as many simple types
2585 - // have the same syntax
2586 - let wrapper = format!("type __T = {};", text);
2587 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
2588 - let fm = cm.new_source_file(
2589 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
2590 - wrapper,
2591 - );
2592 - let mut errors = vec![];
2593 - if let Ok(module) = swc_ecma_parser::parse_file_as_module(
2594 - &fm,
2595 - swc_ecma_parser::Syntax::Typescript(swc_ecma_parser::TsSyntax {
2596 - tsx: true,
2597 - ..Default::default()
2598 - }),
2599 - swc_ecma_ast::EsVersion::latest(),
2600 - None,
2601 - &mut errors,
2602 - ) {
2603 - if let Some(ModuleItem::Stmt(Stmt::Decl(Decl::TsTypeAlias(alias)))) =
2604 - module.body.into_iter().next()
2605 - {
2606 - return *alias.type_ann;
2607 - }
2608 - }
2609 - }
2610 - }
2611 - // Fallback
2612 - TsType::TsKeywordType(TsKeywordType {
2613 - span,
2614 - kind: TsKeywordTypeKind::TsAnyKeyword,
2615 - })
2616 - }
2617 - _ => {
2618 - // Fallback: emit `any` type
2619 - TsType::TsKeywordType(TsKeywordType {
2620 - span,
2621 - kind: TsKeywordTypeKind::TsAnyKeyword,
2622 - })
2623 - }
2624 - }
2625 - }
2626 -
2627 - // ===== Operators =====
2628 -
2629 - fn convert_binary_operator(&self, op: &BinaryOperator) -> BinaryOp {
2630 - match op {
2631 - BinaryOperator::Add => BinaryOp::Add,
2632 - BinaryOperator::Sub => BinaryOp::Sub,
2633 - BinaryOperator::Mul => BinaryOp::Mul,
2634 - BinaryOperator::Div => BinaryOp::Div,
2635 - BinaryOperator::Rem => BinaryOp::Mod,
2636 - BinaryOperator::Exp => BinaryOp::Exp,
2637 - BinaryOperator::Eq => BinaryOp::EqEq,
2638 - BinaryOperator::StrictEq => BinaryOp::EqEqEq,
2639 - BinaryOperator::Neq => BinaryOp::NotEq,
2640 - BinaryOperator::StrictNeq => BinaryOp::NotEqEq,
2641 - BinaryOperator::Lt => BinaryOp::Lt,
2642 - BinaryOperator::Lte => BinaryOp::LtEq,
2643 - BinaryOperator::Gt => BinaryOp::Gt,
2644 - BinaryOperator::Gte => BinaryOp::GtEq,
2645 - BinaryOperator::Shl => BinaryOp::LShift,
2646 - BinaryOperator::Shr => BinaryOp::RShift,
2647 - BinaryOperator::UShr => BinaryOp::ZeroFillRShift,
2648 - BinaryOperator::BitOr => BinaryOp::BitOr,
2649 - BinaryOperator::BitXor => BinaryOp::BitXor,
2650 - BinaryOperator::BitAnd => BinaryOp::BitAnd,
2651 - BinaryOperator::In => BinaryOp::In,
2652 - BinaryOperator::Instanceof => BinaryOp::InstanceOf,
2653 - BinaryOperator::Pipeline => BinaryOp::BitOr, // no pipeline in SWC
2654 - }
2655 - }
2656 -
2657 - fn convert_logical_operator(&self, op: &LogicalOperator) -> BinaryOp {
2658 - match op {
2659 - LogicalOperator::Or => BinaryOp::LogicalOr,
2660 - LogicalOperator::And => BinaryOp::LogicalAnd,
2661 - LogicalOperator::NullishCoalescing => BinaryOp::NullishCoalescing,
2662 - }
2663 - }
2664 -
2665 - fn convert_unary_operator(&self, op: &UnaryOperator) -> UnaryOp {
2666 - match op {
2667 - UnaryOperator::Neg => UnaryOp::Minus,
2668 - UnaryOperator::Plus => UnaryOp::Plus,
2669 - UnaryOperator::Not => UnaryOp::Bang,
2670 - UnaryOperator::BitNot => UnaryOp::Tilde,
2671 - UnaryOperator::TypeOf => UnaryOp::TypeOf,
2672 - UnaryOperator::Void => UnaryOp::Void,
2673 - UnaryOperator::Delete => UnaryOp::Delete,
2674 - UnaryOperator::Throw => UnaryOp::Void, // no throw-as-unary in SWC
2675 - }
2676 - }
2677 -
2678 - fn convert_update_operator(&self, op: &UpdateOperator) -> UpdateOp {
2679 - match op {
2680 - UpdateOperator::Increment => UpdateOp::PlusPlus,
2681 - UpdateOperator::Decrement => UpdateOp::MinusMinus,
2682 - }
2683 - }
2684 -
2685 - fn convert_assignment_operator(&self, op: &AssignmentOperator) -> AssignOp {
2686 - match op {
2687 - AssignmentOperator::Assign => AssignOp::Assign,
2688 - AssignmentOperator::AddAssign => AssignOp::AddAssign,
2689 - AssignmentOperator::SubAssign => AssignOp::SubAssign,
2690 - AssignmentOperator::MulAssign => AssignOp::MulAssign,
2691 - AssignmentOperator::DivAssign => AssignOp::DivAssign,
2692 - AssignmentOperator::RemAssign => AssignOp::ModAssign,
2693 - AssignmentOperator::ExpAssign => AssignOp::ExpAssign,
2694 - AssignmentOperator::ShlAssign => AssignOp::LShiftAssign,
2695 - AssignmentOperator::ShrAssign => AssignOp::RShiftAssign,
2696 - AssignmentOperator::UShrAssign => AssignOp::ZeroFillRShiftAssign,
2697 - AssignmentOperator::BitOrAssign => AssignOp::BitOrAssign,
2698 - AssignmentOperator::BitXorAssign => AssignOp::BitXorAssign,
2699 - AssignmentOperator::BitAndAssign => AssignOp::BitAndAssign,
2700 - AssignmentOperator::OrAssign => AssignOp::OrAssign,
2701 - AssignmentOperator::AndAssign => AssignOp::AndAssign,
2702 - AssignmentOperator::NullishAssign => AssignOp::NullishAssign,
2703 - }
2704 - }
2705 -}
compiler/crates/react_compiler_swc/src/convert_scope.rs deleted
-994
@@ -1,994 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use std::collections::HashMap;
7 -use std::collections::HashSet;
8 -
9 -use indexmap::IndexMap;
10 -use react_compiler_ast::scope::*;
11 -use swc_ecma_ast::*;
12 -use swc_ecma_visit::Visit;
13 -use swc_ecma_visit::VisitWith;
14 -
15 -/// Helper to convert an SWC `Str` node's value to a Rust String.
16 -/// `Str.value` is a `Wtf8Atom` which doesn't implement `Display`,
17 -/// so we go through `Atom` via lossy conversion.
18 -fn str_value_to_string(s: &Str) -> String {
19 - s.value.to_atom_lossy().to_string()
20 -}
21 -
22 -/// Build scope information from an SWC Module AST.
23 -///
24 -/// This performs two passes over the AST:
25 -/// 1. Build the scope tree and collect all bindings
26 -/// 2. Resolve identifier references to their bindings
27 -pub fn build_scope_info(module: &Module) -> ScopeInfo {
28 - // Pass 1: Build scope tree and collect bindings
29 - let mut collector = ScopeCollector::new();
30 - collector.visit_module(module);
31 -
32 - // Pass 2: Resolve references
33 - // We scope the resolver borrow so we can move out of collector afterwards.
34 - // Build ref_node_id_to_binding directly. In SWC, node_id == span.lo
35 - // (unique positions), so the resolver's position-keyed map serves as
36 - // both reference_to_binding and ref_node_id_to_binding.
37 - let ref_node_id_to_binding = {
38 - let mut resolver = ReferenceResolver::new(&collector);
39 - resolver.visit_module(module);
40 -
41 - // Also map declaration identifiers to their bindings
42 - for binding in &collector.bindings {
43 - if let Some(start) = binding.declaration_start {
44 - resolver
45 - .reference_to_binding
46 - .entry(start)
47 - .or_insert(binding.id);
48 - }
49 - }
50 -
51 - // Function redeclarations: resolve the second `function x()`'s ident
52 - // to the first declaration's binding (overwrites any earlier entry).
53 - for (start, binding_id) in &collector.redeclaration_refs {
54 - resolver.reference_to_binding.insert(*start, *binding_id);
55 - }
56 -
57 - resolver.reference_to_binding
58 - };
59 -
60 - let node_id_to_scope = collector.node_to_scope.clone();
61 -
62 - ScopeInfo {
63 - scopes: collector.scopes,
64 - bindings: collector.bindings,
65 - node_to_scope: collector.node_to_scope,
66 - node_to_scope_end: collector.node_to_scope_end,
67 - reference_to_binding: indexmap::IndexMap::new(),
68 - ref_node_id_to_binding,
69 - node_id_to_scope,
70 - program_scope: ScopeId(0),
71 - }
72 -}
73 -
74 -// ── Pass 1: Scope tree + binding collection ─────────────────────────────────
75 -
76 -struct ScopeCollector {
77 - scopes: Vec<ScopeData>,
78 - bindings: Vec<BindingData>,
79 - node_to_scope: HashMap<u32, ScopeId>,
80 - node_to_scope_end: HashMap<u32, u32>,
81 - /// Stack of scope IDs representing the current nesting.
82 - scope_stack: Vec<ScopeId>,
83 - /// Set of span starts for block statements that are direct function/catch bodies.
84 - /// These should NOT create a separate Block scope.
85 - function_body_spans: HashSet<u32>,
86 - /// Function declarations that redeclare an existing hoisted name resolve to
87 - /// the first binding's `BindingId` (like babel's `constantViolations`), so
88 - /// the compiler treats the redeclaration as a reassignment rather than a
89 - /// new binding.
90 - redeclaration_refs: HashMap<u32, BindingId>,
91 -}
92 -
93 -impl ScopeCollector {
94 - fn new() -> Self {
95 - Self {
96 - scopes: Vec::new(),
97 - bindings: Vec::new(),
98 - node_to_scope: HashMap::new(),
99 - node_to_scope_end: HashMap::new(),
100 - scope_stack: Vec::new(),
101 - function_body_spans: HashSet::new(),
102 - redeclaration_refs: HashMap::new(),
103 - }
104 - }
105 -
106 - fn current_scope(&self) -> ScopeId {
107 - *self.scope_stack.last().expect("scope stack is empty")
108 - }
109 -
110 - fn push_scope(&mut self, kind: ScopeKind, node_start: u32, node_end: u32) -> ScopeId {
111 - let id = ScopeId(self.scopes.len() as u32);
112 - let parent = self.scope_stack.last().copied();
113 - self.scopes.push(ScopeData {
114 - id,
115 - parent,
116 - kind,
117 - bindings: HashMap::new(),
118 - });
119 - self.node_to_scope.insert(node_start, id);
120 - if node_end > node_start {
121 - self.node_to_scope_end.insert(node_start, node_end);
122 - }
123 - self.scope_stack.push(id);
124 - id
125 - }
126 -
127 - fn pop_scope(&mut self) {
128 - self.scope_stack.pop();
129 - }
130 -
131 - /// Find the nearest enclosing function or program scope (for hoisting `var` and function decls).
132 - fn enclosing_function_scope(&self) -> ScopeId {
133 - for &scope_id in self.scope_stack.iter().rev() {
134 - let scope = &self.scopes[scope_id.0 as usize];
135 - match scope.kind {
136 - ScopeKind::Function | ScopeKind::Program => return scope_id,
137 - _ => {}
138 - }
139 - }
140 - ScopeId(0)
141 - }
142 -
143 - fn add_binding(
144 - &mut self,
145 - name: String,
146 - kind: BindingKind,
147 - scope: ScopeId,
148 - declaration_type: String,
149 - declaration_start: Option<u32>,
150 - import: Option<ImportBindingData>,
151 - ) -> BindingId {
152 - let id = BindingId(self.bindings.len() as u32);
153 - self.bindings.push(BindingData {
154 - id,
155 - name: name.clone(),
156 - kind,
157 - scope,
158 - declaration_type,
159 - declaration_start,
160 - declaration_node_id: declaration_start,
161 - import,
162 - });
163 - self.scopes[scope.0 as usize].bindings.insert(name, id);
164 - id
165 - }
166 -
167 - /// Extract all binding identifiers from a pattern, adding each as a binding.
168 - fn collect_pat_bindings(
169 - &mut self,
170 - pat: &Pat,
171 - kind: BindingKind,
172 - scope: ScopeId,
173 - declaration_type: &str,
174 - ) {
175 - match pat {
176 - Pat::Ident(binding_ident) => {
177 - let name = binding_ident.id.sym.to_string();
178 - let start = binding_ident.id.span.lo.0;
179 - self.add_binding(
180 - name,
181 - kind,
182 - scope,
183 - declaration_type.to_string(),
184 - Some(start),
185 - None,
186 - );
187 - }
188 - Pat::Array(arr) => {
189 - for elem in &arr.elems {
190 - if let Some(p) = elem {
191 - self.collect_pat_bindings(p, kind.clone(), scope, declaration_type);
192 - }
193 - }
194 - }
195 - Pat::Object(obj) => {
196 - for prop in &obj.props {
197 - match prop {
198 - ObjectPatProp::KeyValue(kv) => {
199 - self.collect_pat_bindings(
200 - &kv.value,
201 - kind.clone(),
202 - scope,
203 - declaration_type,
204 - );
205 - }
206 - ObjectPatProp::Assign(assign) => {
207 - let name = assign.key.sym.to_string();
208 - let start = assign.key.span.lo.0;
209 - self.add_binding(
210 - name,
211 - kind.clone(),
212 - scope,
213 - declaration_type.to_string(),
214 - Some(start),
215 - None,
216 - );
217 - }
218 - ObjectPatProp::Rest(rest) => {
219 - self.collect_pat_bindings(
220 - &rest.arg,
221 - kind.clone(),
222 - scope,
223 - declaration_type,
224 - );
225 - }
226 - }
227 - }
228 - }
229 - Pat::Rest(rest) => {
230 - self.collect_pat_bindings(&rest.arg, kind, scope, declaration_type);
231 - }
232 - Pat::Assign(assign) => {
233 - self.collect_pat_bindings(&assign.left, kind, scope, declaration_type);
234 - }
235 - Pat::Expr(_) | Pat::Invalid(_) => {}
236 - }
237 - }
238 -
239 - /// Visit a function's internals (params + body), creating the function scope.
240 - /// Used for method definitions and other Function nodes not covered by FnDecl/FnExpr.
241 - fn visit_function_inner(&mut self, function: &Function) {
242 - let func_start = function.span.lo.0;
243 - self.push_scope(ScopeKind::Function, func_start, function.span.hi.0);
244 -
245 - for param in &function.params {
246 - self.collect_pat_bindings(
247 - &param.pat,
248 - BindingKind::Param,
249 - self.current_scope(),
250 - "FormalParameter",
251 - );
252 - }
253 -
254 - if let Some(body) = &function.body {
255 - self.function_body_spans.insert(body.span.lo.0);
256 - body.visit_with(self);
257 - }
258 -
259 - self.pop_scope();
260 - }
261 -}
262 -
263 -impl Visit for ScopeCollector {
264 - fn visit_module(&mut self, module: &Module) {
265 - self.push_scope(ScopeKind::Program, module.span.lo.0, module.span.hi.0);
266 - module.visit_children_with(self);
267 - self.pop_scope();
268 - }
269 -
270 - fn visit_import_decl(&mut self, import: &ImportDecl) {
271 - let source = str_value_to_string(&import.src);
272 - let program_scope = ScopeId(0);
273 -
274 - for spec in &import.specifiers {
275 - match spec {
276 - ImportSpecifier::Named(named) => {
277 - let local_name = named.local.sym.to_string();
278 - let start = named.local.span.lo.0;
279 - let imported_name = match &named.imported {
280 - Some(ModuleExportName::Ident(ident)) => Some(ident.sym.to_string()),
281 - Some(ModuleExportName::Str(s)) => Some(str_value_to_string(s)),
282 - None => Some(local_name.clone()),
283 - };
284 - self.add_binding(
285 - local_name,
286 - BindingKind::Module,
287 - program_scope,
288 - "ImportSpecifier".to_string(),
289 - Some(start),
290 - Some(ImportBindingData {
291 - source: source.clone(),
292 - kind: ImportBindingKind::Named,
293 - imported: imported_name,
294 - }),
295 - );
296 - }
297 - ImportSpecifier::Default(default) => {
298 - let local_name = default.local.sym.to_string();
299 - let start = default.local.span.lo.0;
300 - self.add_binding(
301 - local_name,
302 - BindingKind::Module,
303 - program_scope,
304 - "ImportDefaultSpecifier".to_string(),
305 - Some(start),
306 - Some(ImportBindingData {
307 - source: source.clone(),
308 - kind: ImportBindingKind::Default,
309 - imported: None,
310 - }),
311 - );
312 - }
313 - ImportSpecifier::Namespace(ns) => {
314 - let local_name = ns.local.sym.to_string();
315 - let start = ns.local.span.lo.0;
316 - self.add_binding(
317 - local_name,
318 - BindingKind::Module,
319 - program_scope,
320 - "ImportNamespaceSpecifier".to_string(),
321 - Some(start),
322 - Some(ImportBindingData {
323 - source: source.clone(),
324 - kind: ImportBindingKind::Namespace,
325 - imported: None,
326 - }),
327 - );
328 - }
329 - }
330 - }
331 - }
332 -
333 - fn visit_var_decl(&mut self, var_decl: &VarDecl) {
334 - let (kind, declaration_type) = match var_decl.kind {
335 - VarDeclKind::Var => (BindingKind::Var, "VariableDeclarator"),
336 - VarDeclKind::Let => (BindingKind::Let, "VariableDeclarator"),
337 - VarDeclKind::Const => (BindingKind::Const, "VariableDeclarator"),
338 - };
339 -
340 - let target_scope = match var_decl.kind {
341 - VarDeclKind::Var => self.enclosing_function_scope(),
342 - VarDeclKind::Let | VarDeclKind::Const => self.current_scope(),
343 - };
344 -
345 - for declarator in &var_decl.decls {
346 - self.collect_pat_bindings(
347 - &declarator.name,
348 - kind.clone(),
349 - target_scope,
350 - declaration_type,
351 - );
352 - // Visit initializers so nested functions/arrows get their scopes
353 - if let Some(init) = &declarator.init {
354 - init.visit_with(self);
355 - }
356 - }
357 - }
358 -
359 - fn visit_fn_decl(&mut self, fn_decl: &FnDecl) {
360 - // Function declarations are hoisted to the enclosing function/program scope
361 - let hoist_scope = self.enclosing_function_scope();
362 - let name = fn_decl.ident.sym.to_string();
363 - let start = fn_decl.ident.span.lo.0;
364 - if let Some(&existing_id) =
365 - self.scopes[hoist_scope.0 as usize].bindings.get(&name)
366 - {
367 - self.redeclaration_refs.insert(start, existing_id);
368 - } else {
369 - self.add_binding(
370 - name,
371 - BindingKind::Hoisted,
372 - hoist_scope,
373 - "FunctionDeclaration".to_string(),
374 - Some(start),
375 - None,
376 - );
377 - }
378 -
379 - self.visit_function_inner(&fn_decl.function);
380 - }
381 -
382 - fn visit_export_default_decl(&mut self, decl: &ExportDefaultDecl) {
383 - // For `export default function foo(...)`, the function name should be
384 - // hoisted to the enclosing scope (like FnDecl), not bound only in the
385 - // function's own scope (like FnExpr).
386 - match &decl.decl {
387 - DefaultDecl::Fn(fn_expr) => {
388 - if let Some(ident) = &fn_expr.ident {
389 - let hoist_scope = self.enclosing_function_scope();
390 - let name = ident.sym.to_string();
391 - let start = ident.span.lo.0;
392 - self.add_binding(
393 - name,
394 - BindingKind::Hoisted,
395 - hoist_scope,
396 - "FunctionDeclaration".to_string(),
397 - Some(start),
398 - None,
399 - );
400 - }
401 - self.visit_function_inner(&fn_expr.function);
402 - }
403 - DefaultDecl::Class(class_expr) => {
404 - if let Some(ident) = &class_expr.ident {
405 - let name = ident.sym.to_string();
406 - let start = ident.span.lo.0;
407 - self.add_binding(
408 - name,
409 - BindingKind::Local,
410 - self.current_scope(),
411 - "ClassDeclaration".to_string(),
412 - Some(start),
413 - None,
414 - );
415 - }
416 - self.push_scope(
417 - ScopeKind::Class,
418 - class_expr.class.span.lo.0,
419 - class_expr.class.span.hi.0,
420 - );
421 - class_expr.class.visit_children_with(self);
422 - self.pop_scope();
423 - }
424 - DefaultDecl::TsInterfaceDecl(d) => {
425 - d.visit_with(self);
426 - }
427 - }
428 - }
429 -
430 - fn visit_fn_expr(&mut self, fn_expr: &FnExpr) {
431 - let func_start = fn_expr.function.span.lo.0;
432 - self.push_scope(ScopeKind::Function, func_start, fn_expr.function.span.hi.0);
433 -
434 - // Named function expressions bind their name in the function scope
435 - if let Some(ident) = &fn_expr.ident {
436 - let name = ident.sym.to_string();
437 - let start = ident.span.lo.0;
438 - self.add_binding(
439 - name,
440 - BindingKind::Local,
441 - self.current_scope(),
442 - "FunctionExpression".to_string(),
443 - Some(start),
444 - None,
445 - );
446 - }
447 -
448 - for param in &fn_expr.function.params {
449 - self.collect_pat_bindings(
450 - &param.pat,
451 - BindingKind::Param,
452 - self.current_scope(),
453 - "FormalParameter",
454 - );
455 - }
456 -
457 - if let Some(body) = &fn_expr.function.body {
458 - self.function_body_spans.insert(body.span.lo.0);
459 - body.visit_with(self);
460 - }
461 -
462 - self.pop_scope();
463 - }
464 -
465 - fn visit_arrow_expr(&mut self, arrow: &ArrowExpr) {
466 - let func_start = arrow.span.lo.0;
467 - self.push_scope(ScopeKind::Function, func_start, arrow.span.hi.0);
468 -
469 - for param in &arrow.params {
470 - self.collect_pat_bindings(
471 - param,
472 - BindingKind::Param,
473 - self.current_scope(),
474 - "FormalParameter",
475 - );
476 - }
477 -
478 - match &*arrow.body {
479 - BlockStmtOrExpr::BlockStmt(block) => {
480 - self.function_body_spans.insert(block.span.lo.0);
481 - block.visit_with(self);
482 - }
483 - BlockStmtOrExpr::Expr(expr) => {
484 - expr.visit_with(self);
485 - }
486 - }
487 -
488 - self.pop_scope();
489 - }
490 -
491 - fn visit_block_stmt(&mut self, block: &BlockStmt) {
492 - if self.function_body_spans.remove(&block.span.lo.0) {
493 - // This block is a function/catch body — don't create a separate scope
494 - block.visit_children_with(self);
495 - } else {
496 - self.push_scope(ScopeKind::Block, block.span.lo.0, block.span.hi.0);
497 - block.visit_children_with(self);
498 - self.pop_scope();
499 - }
500 - }
501 -
502 - fn visit_for_stmt(&mut self, for_stmt: &ForStmt) {
503 - self.push_scope(ScopeKind::For, for_stmt.span.lo.0, for_stmt.span.hi.0);
504 -
505 - if let Some(init) = &for_stmt.init {
506 - init.visit_with(self);
507 - }
508 - if let Some(test) = &for_stmt.test {
509 - test.visit_with(self);
510 - }
511 - if let Some(update) = &for_stmt.update {
512 - update.visit_with(self);
513 - }
514 - for_stmt.body.visit_with(self);
515 -
516 - self.pop_scope();
517 - }
518 -
519 - fn visit_for_in_stmt(&mut self, for_in: &ForInStmt) {
520 - self.push_scope(ScopeKind::For, for_in.span.lo.0, for_in.span.hi.0);
521 - for_in.left.visit_with(self);
522 - for_in.right.visit_with(self);
523 - for_in.body.visit_with(self);
524 - self.pop_scope();
525 - }
526 -
527 - fn visit_for_of_stmt(&mut self, for_of: &ForOfStmt) {
528 - self.push_scope(ScopeKind::For, for_of.span.lo.0, for_of.span.hi.0);
529 - for_of.left.visit_with(self);
530 - for_of.right.visit_with(self);
531 - for_of.body.visit_with(self);
532 - self.pop_scope();
533 - }
534 -
535 - fn visit_catch_clause(&mut self, catch: &CatchClause) {
536 - self.push_scope(ScopeKind::Catch, catch.span.lo.0, catch.span.hi.0);
537 -
538 - if let Some(param) = &catch.param {
539 - self.collect_pat_bindings(param, BindingKind::Let, self.current_scope(), "CatchClause");
540 - }
541 -
542 - // Mark catch body as already scoped (the catch scope covers it)
543 - self.function_body_spans.insert(catch.body.span.lo.0);
544 - catch.body.visit_with(self);
545 -
546 - self.pop_scope();
547 - }
548 -
549 - fn visit_switch_stmt(&mut self, switch: &SwitchStmt) {
550 - // Visit the discriminant in the outer scope
551 - switch.discriminant.visit_with(self);
552 -
553 - self.push_scope(ScopeKind::Switch, switch.span.lo.0, switch.span.hi.0);
554 - for case in &switch.cases {
555 - case.visit_with(self);
556 - }
557 - self.pop_scope();
558 - }
559 -
560 - fn visit_class_decl(&mut self, class_decl: &ClassDecl) {
561 - let name = class_decl.ident.sym.to_string();
562 - let start = class_decl.ident.span.lo.0;
563 - self.add_binding(
564 - name,
565 - BindingKind::Local,
566 - self.current_scope(),
567 - "ClassDeclaration".to_string(),
568 - Some(start),
569 - None,
570 - );
571 -
572 - self.push_scope(
573 - ScopeKind::Class,
574 - class_decl.class.span.lo.0,
575 - class_decl.class.span.hi.0,
576 - );
577 - class_decl.class.visit_children_with(self);
578 - self.pop_scope();
579 - }
580 -
581 - fn visit_class_expr(&mut self, class_expr: &ClassExpr) {
582 - self.push_scope(
583 - ScopeKind::Class,
584 - class_expr.class.span.lo.0,
585 - class_expr.class.span.hi.0,
586 - );
587 -
588 - if let Some(ident) = &class_expr.ident {
589 - let name = ident.sym.to_string();
590 - let start = ident.span.lo.0;
591 - self.add_binding(
592 - name,
593 - BindingKind::Local,
594 - self.current_scope(),
595 - "ClassExpression".to_string(),
596 - Some(start),
597 - None,
598 - );
599 - }
600 -
601 - class_expr.class.visit_children_with(self);
602 - self.pop_scope();
603 - }
604 -
605 - // Method definitions contain a Function node. We intercept here
606 - // so that the Function gets its own scope with params.
607 - fn visit_function(&mut self, f: &Function) {
608 - // This is reached for object/class methods via default traversal.
609 - self.visit_function_inner(f);
610 - }
611 -}
612 -
613 -// ── Pass 2: Reference resolution ────────────────────────────────────────────
614 -
615 -struct ReferenceResolver<'a> {
616 - scopes: &'a [ScopeData],
617 - #[allow(dead_code)]
618 - bindings: &'a [BindingData],
619 - node_to_scope: &'a HashMap<u32, ScopeId>,
620 - reference_to_binding: IndexMap<u32, BindingId>,
621 - /// Stack of scope IDs for resolution
622 - scope_stack: Vec<ScopeId>,
623 - /// Declaration positions to skip (these are binding sites, not references)
624 - declaration_starts: HashSet<u32>,
625 - /// Span starts for block statements that are direct function/catch bodies.
626 - function_body_spans: HashSet<u32>,
627 -}
628 -
629 -impl<'a> ReferenceResolver<'a> {
630 - fn new(collector: &'a ScopeCollector) -> Self {
631 - let mut declaration_starts = HashSet::new();
632 - for binding in &collector.bindings {
633 - if let Some(start) = binding.declaration_start {
634 - declaration_starts.insert(start);
635 - }
636 - }
637 - Self {
638 - scopes: &collector.scopes,
639 - bindings: &collector.bindings,
640 - node_to_scope: &collector.node_to_scope,
641 - reference_to_binding: IndexMap::new(),
642 - scope_stack: Vec::new(),
643 - declaration_starts,
644 - function_body_spans: HashSet::new(),
645 - }
646 - }
647 -
648 - fn current_scope(&self) -> ScopeId {
649 - *self.scope_stack.last().expect("scope stack is empty")
650 - }
651 -
652 - fn resolve_ident(&mut self, name: &str, start: u32) {
653 - // Skip declaration sites — they'll be added separately
654 - if self.declaration_starts.contains(&start) {
655 - return;
656 - }
657 -
658 - // Walk up the scope chain to find the binding
659 - let mut current = Some(self.current_scope());
660 - while let Some(scope_id) = current {
661 - let scope = &self.scopes[scope_id.0 as usize];
662 - if let Some(&binding_id) = scope.bindings.get(name) {
663 - self.reference_to_binding.insert(start, binding_id);
664 - return;
665 - }
666 - current = scope.parent;
667 - }
668 - // Not found — it's a global, don't record it
669 - }
670 -
671 - fn find_scope_at(&self, node_start: u32) -> Option<&ScopeId> {
672 - self.node_to_scope.get(&node_start)
673 - }
674 -
675 - /// Visit a pattern in parameter position: skip binding idents, but visit
676 - /// default values and computed keys as references.
677 - fn visit_param_pattern(&mut self, pat: &Pat) {
678 - match pat {
679 - Pat::Ident(_) => {
680 - // Declaration — skip
681 - }
682 - Pat::Array(arr) => {
683 - for elem in &arr.elems {
684 - if let Some(p) = elem {
685 - self.visit_param_pattern(p);
686 - }
687 - }
688 - }
689 - Pat::Object(obj) => {
690 - for prop in &obj.props {
691 - match prop {
692 - ObjectPatProp::KeyValue(kv) => {
693 - if let PropName::Computed(computed) = &kv.key {
694 - computed.visit_with(self);
695 - }
696 - self.visit_param_pattern(&kv.value);
697 - }
698 - ObjectPatProp::Assign(assign) => {
699 - if let Some(value) = &assign.value {
700 - value.visit_with(self);
701 - }
702 - }
703 - ObjectPatProp::Rest(rest) => {
704 - self.visit_param_pattern(&rest.arg);
705 - }
706 - }
707 - }
708 - }
709 - Pat::Assign(assign) => {
710 - self.visit_param_pattern(&assign.left);
711 - // Default value IS a reference
712 - assign.right.visit_with(self);
713 - }
714 - Pat::Rest(rest) => {
715 - self.visit_param_pattern(&rest.arg);
716 - }
717 - Pat::Expr(expr) => {
718 - expr.visit_with(self);
719 - }
720 - Pat::Invalid(_) => {}
721 - }
722 - }
723 -
724 - /// Visit function internals for the resolver (params + body), mirroring the collector.
725 - fn visit_function_inner(&mut self, function: &Function) {
726 - let func_start = function.span.lo.0;
727 - if let Some(&scope_id) = self.find_scope_at(func_start) {
728 - self.scope_stack.push(scope_id);
729 -
730 - for param in &function.params {
731 - self.visit_param_pattern(&param.pat);
732 - }
733 -
734 - if let Some(body) = &function.body {
735 - self.function_body_spans.insert(body.span.lo.0);
736 - body.visit_with(self);
737 - }
738 -
739 - self.scope_stack.pop();
740 - }
741 - }
742 -}
743 -
744 -impl<'a> Visit for ReferenceResolver<'a> {
745 - fn visit_module(&mut self, module: &Module) {
746 - self.scope_stack.push(ScopeId(0));
747 - module.visit_children_with(self);
748 - self.scope_stack.pop();
749 - }
750 -
751 - fn visit_ident(&mut self, ident: &Ident) {
752 - let name = ident.sym.to_string();
753 - let start = ident.span.lo.0;
754 - self.resolve_ident(&name, start);
755 - }
756 -
757 - fn visit_import_decl(&mut self, _import: &ImportDecl) {
758 - // Don't recurse — import identifiers are declarations
759 - }
760 -
761 - fn visit_var_decl(&mut self, var_decl: &VarDecl) {
762 - // Only visit initializers, not patterns (which are declarations)
763 - for declarator in &var_decl.decls {
764 - if let Some(init) = &declarator.init {
765 - init.visit_with(self);
766 - }
767 - }
768 - }
769 -
770 - fn visit_fn_decl(&mut self, fn_decl: &FnDecl) {
771 - // Don't resolve the function name — it's a declaration
772 - self.visit_function_inner(&fn_decl.function);
773 - }
774 -
775 - fn visit_export_default_decl(&mut self, decl: &ExportDefaultDecl) {
776 - // Mirror the collector: handle exported functions/classes with their own
777 - // scope logic, rather than falling through to the default FnExpr visitor.
778 - match &decl.decl {
779 - DefaultDecl::Fn(fn_expr) => {
780 - // Don't resolve the function name — it's a declaration
781 - self.visit_function_inner(&fn_expr.function);
782 - }
783 - DefaultDecl::Class(class_expr) => {
784 - if let Some(&scope_id) = self.find_scope_at(class_expr.class.span.lo.0) {
785 - self.scope_stack.push(scope_id);
786 - class_expr.class.visit_children_with(self);
787 - self.scope_stack.pop();
788 - }
789 - }
790 - DefaultDecl::TsInterfaceDecl(d) => {
791 - d.visit_with(self);
792 - }
793 - }
794 - }
795 -
796 - fn visit_fn_expr(&mut self, fn_expr: &FnExpr) {
797 - let func_start = fn_expr.function.span.lo.0;
798 - if let Some(&scope_id) = self.find_scope_at(func_start) {
799 - self.scope_stack.push(scope_id);
800 -
801 - // Don't resolve named fn expr ident — it's a declaration
802 -
803 - for param in &fn_expr.function.params {
804 - self.visit_param_pattern(&param.pat);
805 - }
806 -
807 - if let Some(body) = &fn_expr.function.body {
808 - self.function_body_spans.insert(body.span.lo.0);
809 - body.visit_with(self);
810 - }
811 -
812 - self.scope_stack.pop();
813 - }
814 - }
815 -
816 - fn visit_arrow_expr(&mut self, arrow: &ArrowExpr) {
817 - let func_start = arrow.span.lo.0;
818 - if let Some(&scope_id) = self.find_scope_at(func_start) {
819 - self.scope_stack.push(scope_id);
820 -
821 - for param in &arrow.params {
822 - self.visit_param_pattern(param);
823 - }
824 -
825 - match &*arrow.body {
826 - BlockStmtOrExpr::BlockStmt(block) => {
827 - self.function_body_spans.insert(block.span.lo.0);
828 - block.visit_with(self);
829 - }
830 - BlockStmtOrExpr::Expr(expr) => {
831 - expr.visit_with(self);
832 - }
833 - }
834 -
835 - self.scope_stack.pop();
836 - }
837 - }
838 -
839 - fn visit_block_stmt(&mut self, block: &BlockStmt) {
840 - if self.function_body_spans.remove(&block.span.lo.0) {
841 - // Function/catch body — scope already pushed
842 - block.visit_children_with(self);
843 - } else if let Some(&scope_id) = self.find_scope_at(block.span.lo.0) {
844 - self.scope_stack.push(scope_id);
845 - block.visit_children_with(self);
846 - self.scope_stack.pop();
847 - } else {
848 - block.visit_children_with(self);
849 - }
850 - }
851 -
852 - fn visit_for_stmt(&mut self, for_stmt: &ForStmt) {
853 - if let Some(&scope_id) = self.find_scope_at(for_stmt.span.lo.0) {
854 - self.scope_stack.push(scope_id);
855 -
856 - if let Some(init) = &for_stmt.init {
857 - init.visit_with(self);
858 - }
859 - if let Some(test) = &for_stmt.test {
860 - test.visit_with(self);
861 - }
862 - if let Some(update) = &for_stmt.update {
863 - update.visit_with(self);
864 - }
865 - for_stmt.body.visit_with(self);
866 -
867 - self.scope_stack.pop();
868 - }
869 - }
870 -
871 - fn visit_for_in_stmt(&mut self, for_in: &ForInStmt) {
872 - if let Some(&scope_id) = self.find_scope_at(for_in.span.lo.0) {
873 - self.scope_stack.push(scope_id);
874 - for_in.left.visit_with(self);
875 - for_in.right.visit_with(self);
876 - for_in.body.visit_with(self);
877 - self.scope_stack.pop();
878 - }
879 - }
880 -
881 - fn visit_for_of_stmt(&mut self, for_of: &ForOfStmt) {
882 - if let Some(&scope_id) = self.find_scope_at(for_of.span.lo.0) {
883 - self.scope_stack.push(scope_id);
884 - for_of.left.visit_with(self);
885 - for_of.right.visit_with(self);
886 - for_of.body.visit_with(self);
887 - self.scope_stack.pop();
888 - }
889 - }
890 -
891 - fn visit_catch_clause(&mut self, catch: &CatchClause) {
892 - if let Some(&scope_id) = self.find_scope_at(catch.span.lo.0) {
893 - self.scope_stack.push(scope_id);
894 - // Don't visit catch param — it's a declaration
895 - self.function_body_spans.insert(catch.body.span.lo.0);
896 - catch.body.visit_with(self);
897 - self.scope_stack.pop();
898 - }
899 - }
900 -
901 - fn visit_switch_stmt(&mut self, switch: &SwitchStmt) {
902 - switch.discriminant.visit_with(self);
903 -
904 - if let Some(&scope_id) = self.find_scope_at(switch.span.lo.0) {
905 - self.scope_stack.push(scope_id);
906 - for case in &switch.cases {
907 - case.visit_with(self);
908 - }
909 - self.scope_stack.pop();
910 - }
911 - }
912 -
913 - fn visit_class_decl(&mut self, class_decl: &ClassDecl) {
914 - // Don't resolve the class name — it's a declaration
915 - if let Some(&scope_id) = self.find_scope_at(class_decl.class.span.lo.0) {
916 - self.scope_stack.push(scope_id);
917 - class_decl.class.visit_children_with(self);
918 - self.scope_stack.pop();
919 - }
920 - }
921 -
922 - fn visit_class_expr(&mut self, class_expr: &ClassExpr) {
923 - if let Some(&scope_id) = self.find_scope_at(class_expr.class.span.lo.0) {
924 - self.scope_stack.push(scope_id);
925 - // Don't resolve named class expr ident — it's a declaration
926 - class_expr.class.visit_children_with(self);
927 - self.scope_stack.pop();
928 - }
929 - }
930 -
931 - fn visit_function(&mut self, f: &Function) {
932 - // Reached for object/class methods via default traversal
933 - self.visit_function_inner(f);
934 - }
935 -
936 - // Don't resolve property idents on member expressions as references
937 - fn visit_member_expr(&mut self, member: &MemberExpr) {
938 - member.obj.visit_with(self);
939 - if let MemberProp::Computed(computed) = &member.prop {
940 - computed.visit_with(self);
941 - }
942 - }
943 -
944 - // Handle property definitions — don't resolve non-computed keys
945 - fn visit_prop(&mut self, prop: &Prop) {
946 - match prop {
947 - Prop::Shorthand(ident) => {
948 - // Shorthand property `{ x }` — `x` is a reference
949 - self.visit_ident(ident);
950 - }
951 - Prop::KeyValue(kv) => {
952 - if let PropName::Computed(computed) = &kv.key {
953 - computed.visit_with(self);
954 - }
955 - kv.value.visit_with(self);
956 - }
957 - Prop::Assign(assign) => {
958 - assign.value.visit_with(self);
959 - }
960 - Prop::Getter(getter) => {
961 - if let PropName::Computed(computed) = &getter.key {
962 - computed.visit_with(self);
963 - }
964 - if let Some(body) = &getter.body {
965 - body.visit_with(self);
966 - }
967 - }
968 - Prop::Setter(setter) => {
969 - if let PropName::Computed(computed) = &setter.key {
970 - computed.visit_with(self);
971 - }
972 - setter.param.visit_with(self);
973 - if let Some(body) = &setter.body {
974 - body.visit_with(self);
975 - }
976 - }
977 - Prop::Method(method) => {
978 - if let PropName::Computed(computed) = &method.key {
979 - computed.visit_with(self);
980 - }
981 - method.function.visit_with(self);
982 - }
983 - }
984 - }
985 -
986 - // Don't resolve labels
987 - fn visit_labeled_stmt(&mut self, labeled: &LabeledStmt) {
988 - labeled.body.visit_with(self);
989 - }
990 -
991 - fn visit_break_stmt(&mut self, _break_stmt: &BreakStmt) {}
992 -
993 - fn visit_continue_stmt(&mut self, _continue_stmt: &ContinueStmt) {}
994 -}
compiler/crates/react_compiler_swc/src/diagnostics.rs deleted
-108
@@ -1,108 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use react_compiler::entrypoint::compile_result::{
7 - CompileResult, CompilerErrorDetailInfo, CompilerErrorInfo, LoggerEvent,
8 -};
9 -
10 -#[derive(Debug, Clone)]
11 -pub enum Severity {
12 - Error,
13 - Warning,
14 -}
15 -
16 -#[derive(Debug, Clone)]
17 -pub struct DiagnosticMessage {
18 - pub severity: Severity,
19 - pub message: String,
20 - pub span: Option<(u32, u32)>,
21 -}
22 -
23 -/// Converts a CompileResult into diagnostic messages for display
24 -pub fn compile_result_to_diagnostics(result: &CompileResult) -> Vec<DiagnosticMessage> {
25 - let mut diagnostics = Vec::new();
26 -
27 - match result {
28 - CompileResult::Success { events, .. } => {
29 - // Process logger events from successful compilation
30 - for event in events {
31 - if let Some(diag) = event_to_diagnostic(event) {
32 - diagnostics.push(diag);
33 - }
34 - }
35 - }
36 - CompileResult::Error {
37 - error, events, ..
38 - } => {
39 - // Add the main error
40 - diagnostics.push(error_info_to_diagnostic(error));
41 -
42 - // Process logger events from failed compilation
43 - for event in events {
44 - if let Some(diag) = event_to_diagnostic(event) {
45 - diagnostics.push(diag);
46 - }
47 - }
48 - }
49 - }
50 -
51 - diagnostics
52 -}
53 -
54 -fn error_info_to_diagnostic(error: &CompilerErrorInfo) -> DiagnosticMessage {
55 - let message = if let Some(description) = &error.description {
56 - format!("[ReactCompiler] {}. {}", error.reason, description)
57 - } else {
58 - format!("[ReactCompiler] {}", error.reason)
59 - };
60 -
61 - DiagnosticMessage {
62 - severity: Severity::Error,
63 - message,
64 - span: None,
65 - }
66 -}
67 -
68 -fn error_detail_to_diagnostic(detail: &CompilerErrorDetailInfo, is_error: bool) -> DiagnosticMessage {
69 - let message = if let Some(description) = &detail.description {
70 - format!(
71 - "[ReactCompiler] {}: {}. {}",
72 - detail.category, detail.reason, description
73 - )
74 - } else {
75 - format!("[ReactCompiler] {}: {}", detail.category, detail.reason)
76 - };
77 -
78 - DiagnosticMessage {
79 - severity: if is_error {
80 - Severity::Error
81 - } else {
82 - Severity::Warning
83 - },
84 - message,
85 - span: None,
86 - }
87 -}
88 -
89 -fn event_to_diagnostic(event: &LoggerEvent) -> Option<DiagnosticMessage> {
90 - match event {
91 - LoggerEvent::CompileSuccess { .. } => None,
92 - LoggerEvent::CompileSkip { .. } => None,
93 - LoggerEvent::CompileError { detail, .. }
94 - | LoggerEvent::CompileErrorWithLoc { detail, .. } => {
95 - Some(error_detail_to_diagnostic(detail, false))
96 - }
97 - LoggerEvent::CompileUnexpectedThrow { data, .. } => Some(DiagnosticMessage {
98 - severity: Severity::Error,
99 - message: format!("[ReactCompiler] Unexpected error: {}", data),
100 - span: None,
101 - }),
102 - LoggerEvent::PipelineError { data, .. } => Some(DiagnosticMessage {
103 - severity: Severity::Error,
104 - message: format!("[ReactCompiler] Pipeline error: {}", data),
105 - span: None,
106 - }),
107 - }
108 -}
compiler/crates/react_compiler_swc/src/lib.rs deleted
-1259
@@ -1,1259 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -pub mod convert_ast;
7 -pub mod convert_ast_reverse;
8 -pub mod convert_scope;
9 -pub mod apply_renames;
10 -pub mod diagnostics;
11 -pub mod prefilter;
12 -pub(crate) mod ts_namespace_export_fixup;
13 -
14 -use apply_renames::apply_renames;
15 -use convert_ast::convert_module_with_source_type;
16 -use convert_ast_reverse::convert_program_to_swc_with_source;
17 -use convert_scope::build_scope_info;
18 -use diagnostics::{compile_result_to_diagnostics, DiagnosticMessage};
19 -use prefilter::has_react_like_functions;
20 -use react_compiler::entrypoint::compile_result::LoggerEvent;
21 -use react_compiler::entrypoint::plugin_options::PluginOptions;
22 -use std::cell::RefCell;
23 -use swc_common::comments::Comments;
24 -
25 -/// Describes where a blank line should be inserted relative to a body item.
26 -#[derive(Clone, Debug)]
27 -pub enum BlankLinePosition {
28 - /// Insert blank line before the item (including its leading comments).
29 - /// The `first_code_line` is the item's first code line (without comments)
30 - /// used as a search anchor in the output.
31 - BeforeItem { first_code_line: String },
32 - /// Insert blank line between the item's leading comments and its code.
33 - /// The `first_code_line` is used to find where the code starts.
34 - BeforeCode { first_code_line: String },
35 -}
36 -
37 -thread_local! {
38 - /// Thread-local storage for comments from the last compilation.
39 - /// Used by `emit` to include comments without API changes.
40 - static LAST_COMMENTS: RefCell<Option<swc_common::comments::SingleThreadedComments>> = RefCell::new(None);
41 -
42 - /// Thread-local storage for blank line positions.
43 - /// Contains information about where to insert blank lines during emit.
44 - static BLANK_LINE_POSITIONS: RefCell<Vec<BlankLinePosition>> = RefCell::new(Vec::new());
45 -}
46 -
47 -/// Result of compiling a program via the SWC frontend.
48 -pub struct TransformResult {
49 - /// The compiled program as an SWC Module (None if no changes needed).
50 - pub module: Option<swc_ecma_ast::Module>,
51 - /// Comments extracted from the compiled AST (for use with `emit_with_comments`).
52 - pub comments: Option<swc_common::comments::SingleThreadedComments>,
53 - pub diagnostics: Vec<DiagnosticMessage>,
54 - pub events: Vec<LoggerEvent>,
55 -}
56 -
57 -/// Result of linting a program via the SWC frontend.
58 -pub struct LintResult {
59 - pub diagnostics: Vec<DiagnosticMessage>,
60 -}
61 -
62 -/// Primary transform API — accepts pre-parsed SWC Module.
63 -pub fn transform(
64 - module: &swc_ecma_ast::Module,
65 - source_text: &str,
66 - options: PluginOptions,
67 -) -> TransformResult {
68 - if options.compilation_mode != "all" && !has_react_like_functions(module) {
69 - return TransformResult {
70 - module: None,
71 - comments: None,
72 - diagnostics: vec![],
73 - events: vec![],
74 - };
75 - }
76 -
77 - // Detect source type from pragma. The @script pragma indicates
78 - // CommonJS (script) mode, which affects how imports are emitted.
79 - let source_type = if source_text
80 - .lines()
81 - .next()
82 - .map_or(false, |line| line.contains("@script"))
83 - {
84 - react_compiler_ast::SourceType::Script
85 - } else {
86 - react_compiler_ast::SourceType::Module
87 - };
88 - let file = convert_module_with_source_type(module, source_text, source_type);
89 - let scope_info = build_scope_info(module);
90 - let result =
91 - react_compiler::entrypoint::program::compile_program(file, scope_info, options);
92 -
93 - let diagnostics = compile_result_to_diagnostics(&result);
94 - let (program_ast, events, renames) = match result {
95 - react_compiler::entrypoint::compile_result::CompileResult::Success {
96 - ast,
97 - events,
98 - renames,
99 - ..
100 - } => (ast, events, renames),
101 - react_compiler::entrypoint::compile_result::CompileResult::Error {
102 - events, ..
103 - } => (None, events, Vec::new()),
104 - };
105 -
106 - let conversion_result = program_ast.map(|file| {
107 - convert_program_to_swc_with_source(&file, Some(source_text))
108 - });
109 -
110 - let (mut swc_module, mut comments) = match conversion_result {
111 - Some(result) => (Some(result.module), Some(result.comments)),
112 - None if !renames.is_empty() => (Some(module.clone()), None),
113 - None => (None, None),
114 - };
115 -
116 - // If we have a compiled module, extract comments from the original source
117 - // and merge them into the comment map. The Rust compiler does not preserve
118 - // comments in its output, so we re-extract them from the source text.
119 - if let Some(ref mut swc_mod) = swc_module {
120 - use swc_common::Spanned;
121 -
122 - // Compute blank line positions BEFORE span fixup, while spans still
123 - // reflect original source positions. Babel's generator adds blank
124 - // lines between consecutive items when the original source had blank
125 - // lines between them (i.e., endLine(prev) + 1 < startLine(next)).
126 - let blank_line_positions =
127 - compute_blank_line_positions(&swc_mod.body, source_text);
128 -
129 - // Fix up dummy spans on compiler-generated items: SWC codegen skips
130 - // comments at BytePos(0) (DUMMY), so we give generated items a real
131 - // span before the original module's first item.
132 - let first_source_lo = module.body.first().map(|item| item.span().lo);
133 - let mut top_level_comment_target = None;
134 - if first_source_lo.is_some() {
135 - let mut next_synthetic_pos = swc_common::BytePos(1);
136 - for item in &mut swc_mod.body {
137 - if item.span().lo.is_dummy() {
138 - let synthetic_span =
139 - swc_common::Span::new(next_synthetic_pos, next_synthetic_pos);
140 - next_synthetic_pos = next_synthetic_pos + swc_common::BytePos(1);
141 - match item {
142 - swc_ecma_ast::ModuleItem::ModuleDecl(
143 - swc_ecma_ast::ModuleDecl::Import(import),
144 - ) => {
145 - import.span = synthetic_span;
146 - top_level_comment_target = Some(import.span.hi);
147 - }
148 - swc_ecma_ast::ModuleItem::Stmt(
149 - swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Var(var)),
150 - ) => {
151 - var.span = synthetic_span;
152 - }
153 - _ => {}
154 - }
155 - }
156 - }
157 - }
158 -
159 - apply_renames(swc_mod, &renames);
160 -
161 - let (source_leading_comments, source_trailing_comments) =
162 - extract_source_comments(source_text);
163 - if !source_leading_comments.is_empty() || !source_trailing_comments.is_empty() {
164 - let merged = comments.unwrap_or_default();
165 -
166 - let source_bytes = source_text.as_bytes();
167 - for (orig_pos, comment_list) in source_leading_comments {
168 - // Pragma comments (e.g. `// @gating`) before the first source
169 - // item need to attach AFTER any compiler-inserted imports so
170 - // the gated output preserves the directive. Other leading
171 - // comments (copyright, JSDoc, etc.) stay at their original
172 - // position so SWC emits them before the original item.
173 - let is_pragma = Some(orig_pos) == first_source_lo
174 - && comment_list
175 - .iter()
176 - .all(|c| c.text.trim_start().starts_with('@'));
177 - if is_pragma {
178 - if let Some(pos) = top_level_comment_target {
179 - merged.add_trailing_comments(pos, comment_list);
180 - continue;
181 - }
182 - }
183 - merged.add_leading_comments(orig_pos, comment_list);
184 - }
185 - // Trailing comments after a `,` separator are stored by the SWC
186 - // parser at the position past the comma, but codegen looks them
187 - // up at the previous element's `span.hi`, which is before the
188 - // comma. Shift those back by one. Trailing comments after other
189 - // tokens (e.g. `;`) are already at the matching `span.hi`, so
190 - // pass them through unchanged.
191 - for (orig_pos, comment_list) in source_trailing_comments {
192 - let idx = orig_pos.0 as usize;
193 - let pos = if idx >= 2 && source_bytes.get(idx - 2) == Some(&b',') {
194 - swc_common::BytePos(orig_pos.0 - 1)
195 - } else {
196 - orig_pos
197 - };
198 - merged.add_trailing_comments(pos, comment_list);
199 - }
200 - comments = Some(merged);
201 - }
202 -
203 - // Store blank line positions in thread-local for `emit` to use
204 - BLANK_LINE_POSITIONS.with(|cell| {
205 - *cell.borrow_mut() = blank_line_positions;
206 - });
207 - }
208 -
209 - // Store comments in thread-local for `emit` to use
210 - LAST_COMMENTS.with(|cell| {
211 - *cell.borrow_mut() = comments.clone();
212 - });
213 -
214 - TransformResult {
215 - module: swc_module,
216 - comments,
217 - diagnostics,
218 - events,
219 - }
220 -}
221 -
222 -/// Convenience wrapper — parses source text, then transforms.
223 -pub fn transform_source(source_text: &str, options: PluginOptions) -> TransformResult {
224 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
225 - let fm = cm.new_source_file(
226 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
227 - source_text.to_string(),
228 - );
229 -
230 - let mut errors = vec![];
231 - let module = swc_ecma_parser::parse_file_as_module(
232 - &fm,
233 - swc_ecma_parser::Syntax::Es(swc_ecma_parser::EsSyntax {
234 - jsx: true,
235 - ..Default::default()
236 - }),
237 - swc_ecma_ast::EsVersion::latest(),
238 - None,
239 - &mut errors,
240 - );
241 -
242 - match module {
243 - Ok(module) => transform(&module, source_text, options),
244 - Err(_) => TransformResult {
245 - module: None,
246 - comments: None,
247 - diagnostics: vec![],
248 - events: vec![],
249 - },
250 - }
251 -}
252 -
253 -/// Lint API — same as transform but only collects diagnostics, no AST output.
254 -pub fn lint(
255 - module: &swc_ecma_ast::Module,
256 - source_text: &str,
257 - options: PluginOptions,
258 -) -> LintResult {
259 - let mut opts = options;
260 - opts.no_emit = true;
261 -
262 - let result = transform(module, source_text, opts);
263 - LintResult {
264 - diagnostics: result.diagnostics,
265 - }
266 -}
267 -
268 -/// Emit an SWC Module to a string via swc_ecma_codegen.
269 -/// If `transform` was called on the same thread, any comments from the
270 -/// compiled AST are automatically included.
271 -pub fn emit(module: &swc_ecma_ast::Module) -> String {
272 - LAST_COMMENTS.with(|cell| {
273 - let borrowed = cell.borrow();
274 - let positions = BLANK_LINE_POSITIONS.with(|bl| bl.borrow().clone());
275 - emit_with_comments(module, borrowed.as_ref(), &positions)
276 - })
277 -}
278 -
279 -/// Emit an SWC Module to a string, optionally including comments.
280 -/// `blank_line_positions` describes where blank lines should be inserted
281 -/// to match Babel's blank line behavior.
282 -pub fn emit_with_comments(
283 - module: &swc_ecma_ast::Module,
284 - comments: Option<&swc_common::comments::SingleThreadedComments>,
285 - blank_line_positions: &[BlankLinePosition],
286 -) -> String {
287 - // Standard emit path
288 - let code = emit_module_to_string(module, comments);
289 - let code = fix_block_comment_newlines(&code);
290 -
291 - // Add blank lines after directives to match Babel's codegen behavior.
292 - // Babel always emits a blank line after the last directive in a
293 - // program/function body.
294 - let code = add_blank_lines_after_directives(&code);
295 -
296 - // Reposition blank lines that SWC places before comment blocks:
297 - // SWC emits blank lines before leading comments, but Babel places
298 - // them after the comments (between comments and the declaration).
299 - // Move blank lines from before comment blocks to after them when
300 - // the comment block is followed by a top-level declaration.
301 - let code = reposition_comment_blank_lines(&code);
302 -
303 - // Expand single-line object literals to multi-line format in
304 - // FIXTURE_ENTRYPOINT-style structures. SWC codegen emits small objects
305 - // on single lines while Babel puts them on multiple lines. Prettier
306 - // preserves this choice, causing formatting differences.
307 - let code = expand_fixture_entrypoint_objects(&code);
308 -
309 - if blank_line_positions.is_empty() || module.body.is_empty() {
310 - return code;
311 - }
312 -
313 - // Insert blank lines between top-level declarations to match Babel's
314 - // output. Babel's generator preserves blank lines from the original
315 - // source between consecutive top-level items.
316 - insert_blank_lines_in_output(&code, blank_line_positions)
317 -}
318 -
319 -/// Emit a full module to a string.
320 -///
321 -/// Records a source map during emission so the namespace-export fixup can
322 -/// anchor its line rewrites to the module items that produced them (see
323 -/// `ts_namespace_export_fixup`).
324 -fn emit_module_to_string(
325 - module: &swc_ecma_ast::Module,
326 - comments: Option<&swc_common::comments::SingleThreadedComments>,
327 -) -> String {
328 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
329 - let mut buf = vec![];
330 - let mut srcmap: Vec<(swc_common::BytePos, swc_common::LineCol)> = Vec::new();
331 - {
332 - let wr = swc_ecma_codegen::text_writer::JsWriter::new(
333 - cm.clone(),
334 - "\n",
335 - &mut buf,
336 - Some(&mut srcmap),
337 - );
338 - let mut emitter = swc_ecma_codegen::Emitter {
339 - cfg: swc_ecma_codegen::Config::default().with_minify(false),
340 - cm,
341 - comments: comments.map(|c| c as &dyn swc_common::comments::Comments),
342 - wr: Box::new(wr),
343 - };
344 - swc_ecma_codegen::Node::emit_with(module, &mut emitter).unwrap();
345 - }
346 - let code = String::from_utf8(buf).unwrap();
347 - ts_namespace_export_fixup::fix_ts_namespace_export_decls(&module.body, &code, &srcmap)
348 -}
349 -
350 -/// Insert blank lines into the emitted output at positions specified by
351 -/// `blank_line_positions`. Each position includes a `first_code_line` that
352 -/// identifies the item's first line of code (without comments), used as
353 -/// a search anchor in the output.
354 -fn insert_blank_lines_in_output(
355 - code: &str,
356 - positions: &[BlankLinePosition],
357 -) -> String {
358 - if positions.is_empty() {
359 - return code.to_string();
360 - }
361 -
362 - let lines: Vec<&str> = code.lines().collect();
363 -
364 - // Phase 1: Find which output line indices need a blank line inserted
365 - // BEFORE them. We do this by finding each target's first_code_line in
366 - // the output, then computing the actual insert line.
367 - let mut insert_before: Vec<usize> = Vec::new();
368 - let mut used_lines: Vec<bool> = vec![false; lines.len()];
369 -
370 - for pos in positions {
371 - let (first_code_line, before_comments) = match pos {
372 - BlankLinePosition::BeforeItem { first_code_line } => {
373 - (first_code_line.as_str(), true)
374 - }
375 - BlankLinePosition::BeforeCode { first_code_line } => {
376 - (first_code_line.as_str(), false)
377 - }
378 - };
379 -
380 - // Find this code line in the output (first unused match).
381 - // For BeforeCode positions, also allow matching already-used lines
382 - // since BeforeItem and BeforeCode may target the same code line.
383 - let mut found_idx = None;
384 - for (i, &line) in lines.iter().enumerate() {
385 - if line == first_code_line && (!used_lines[i] || !before_comments) {
386 - found_idx = Some(i);
387 - if !used_lines[i] {
388 - used_lines[i] = true;
389 - }
390 - break;
391 - }
392 - }
393 -
394 - let code_line_idx = match found_idx {
395 - Some(idx) => idx,
396 - None => continue,
397 - };
398 -
399 - let insert_line = if before_comments {
400 - // BeforeItem: insert before the comment block that precedes
401 - // this code line
402 - find_comment_block_start(&lines, code_line_idx)
403 - } else {
404 - // BeforeCode: insert right before the code line itself
405 - code_line_idx
406 - };
407 -
408 - // Only insert if the previous line is not already blank
409 - if insert_line > 0 && !lines[insert_line - 1].trim().is_empty() {
410 - insert_before.push(insert_line);
411 - }
412 - }
413 -
414 - if insert_before.is_empty() {
415 - return code.to_string();
416 - }
417 -
418 - insert_before.sort_unstable();
419 - insert_before.dedup();
420 -
421 - // Phase 2: Build the result with blank lines inserted
422 - let mut result = String::with_capacity(code.len() + insert_before.len() * 2);
423 - let mut insert_idx = 0;
424 -
425 - for (line_idx, &line) in lines.iter().enumerate() {
426 - // Check if we need to insert a blank line before this line
427 - if insert_idx < insert_before.len() && insert_before[insert_idx] == line_idx {
428 - result.push('\n');
429 - insert_idx += 1;
430 - }
431 -
432 - result.push_str(line);
433 - if line_idx < lines.len() - 1 || code.ends_with('\n') {
434 - result.push('\n');
435 - }
436 - }
437 -
438 - result
439 -}
440 -
441 -/// Find the start of a comment block that precedes the line at `code_line_idx`.
442 -/// Walks backwards from `code_line_idx - 1` as long as lines are comment
443 -/// lines (starting with `//`, `/*`, ` *`, `*/`, or `/**`).
444 -fn find_comment_block_start(lines: &[&str], code_line_idx: usize) -> usize {
445 - let mut start = code_line_idx;
446 - let mut i = code_line_idx;
447 - while i > 0 {
448 - i -= 1;
449 - let trimmed = lines[i].trim();
450 - if trimmed.is_empty() {
451 - break; // blank line, stop
452 - }
453 - if trimmed.starts_with("//")
454 - || trimmed.starts_with("/*")
455 - || trimmed.starts_with("* ")
456 - || trimmed.starts_with("*/")
457 - || trimmed == "*"
458 - {
459 - start = i;
460 - } else {
461 - break;
462 - }
463 - }
464 - start
465 -}
466 -
467 -/// Add blank lines after directive sequences in function/program bodies.
468 -///
469 -/// Babel's codegen emits a blank line after the last directive in a body
470 -/// (e.g., after `"use strict";` or `"use no memo";`). SWC's codegen
471 -/// does not. This function adds those blank lines to match Babel's output.
472 -fn add_blank_lines_after_directives(code: &str) -> String {
473 - let lines: Vec<&str> = code.lines().collect();
474 - if lines.is_empty() {
475 - return code.to_string();
476 - }
477 -
478 - let mut result: Vec<&str> = Vec::with_capacity(lines.len() + 8);
479 - let mut i = 0;
480 -
481 - while i < lines.len() {
482 - result.push(lines[i]);
483 -
484 - // Check if this line is a directive (string literal expression statement)
485 - if is_directive_line(lines[i]) {
486 - // Check if the next line is NOT a directive and NOT blank
487 - if i + 1 < lines.len()
488 - && !is_directive_line(lines[i + 1])
489 - && !lines[i + 1].trim().is_empty()
490 - {
491 - result.push("");
492 - }
493 - }
494 -
495 - i += 1;
496 - }
497 -
498 - // Rejoin, preserving trailing newline if present
499 - let mut output = result.join("\n");
500 - if code.ends_with('\n') && !output.ends_with('\n') {
501 - output.push('\n');
502 - }
503 - output
504 -}
505 -
506 -/// Check if a line is a directive (a string literal expression statement).
507 -/// Directives look like: `"use strict";` or `'use no memo';` possibly with
508 -/// leading whitespace (indentation for function body directives).
509 -fn is_directive_line(line: &str) -> bool {
510 - let trimmed = line.trim();
511 - // Must start with a quote and end with the matching quote + semicolon
512 - if let Some(rest) = trimmed.strip_prefix('"') {
513 - rest.ends_with("\";")
514 - } else if let Some(rest) = trimmed.strip_prefix('\'') {
515 - rest.ends_with("';")
516 - } else {
517 - false
518 - }
519 -}
520 -
521 -/// Insert newlines after `*/` when followed by code on the same line.
522 -/// Only applies to multiline block comments (JSDoc-style), not inline ones.
523 -fn fix_block_comment_newlines(code: &str) -> String {
524 - let mut result = String::with_capacity(code.len());
525 - let mut chars = code.char_indices().peekable();
526 - let bytes = code.as_bytes();
527 - let mut in_block_comment = false;
528 - let mut block_comment_multiline = false;
529 -
530 - while let Some((i, c)) = chars.next() {
531 - // Track block comment state
532 - if !in_block_comment && c == '/' && bytes.get(i + 1) == Some(&b'*') {
533 - in_block_comment = true;
534 - block_comment_multiline = false;
535 - result.push(c);
536 - continue;
537 - }
538 -
539 - if in_block_comment {
540 - if c == '\n' {
541 - block_comment_multiline = true;
542 - }
543 - result.push(c);
544 -
545 - // Check for end of block comment
546 - if c == '*' && bytes.get(i + 1) == Some(&b'/') {
547 - chars.next();
548 - result.push('/');
549 - in_block_comment = false;
550 -
551 - if block_comment_multiline {
552 - // Skip spaces after `*/`
553 - let mut spaces = String::new();
554 - while let Some(&(_, next_c)) = chars.peek() {
555 - if next_c == ' ' || next_c == '\t' {
556 - spaces.push(next_c);
557 - chars.next();
558 - } else {
559 - break;
560 - }
561 - }
562 -
563 - // If followed by code on the same line, insert newline
564 - if let Some(&(_, next_c)) = chars.peek() {
565 - if next_c != '\n' && next_c != '\r' {
566 - result.push('\n');
567 - } else {
568 - result.push_str(&spaces);
569 - }
570 - } else {
571 - result.push_str(&spaces);
572 - }
573 - }
574 - }
575 - continue;
576 - }
577 -
578 - result.push(c);
579 - }
580 - result
581 -}
582 -
583 -/// Reposition blank lines from before comment blocks to after them.
584 -///
585 -/// SWC's codegen sometimes places blank lines before leading comment blocks,
586 -/// but Babel's generator places them after the comments (between the comment
587 -/// block and the declaration). This function detects the pattern:
588 -///
589 -/// <non-comment line>
590 -/// <blank line>
591 -/// <comment lines...>
592 -/// <declaration line>
593 -///
594 -/// And transforms it to:
595 -///
596 -/// <non-comment line>
597 -/// <comment lines...>
598 -/// <blank line>
599 -/// <declaration line>
600 -///
601 -/// This only applies to top-level (non-indented) comment blocks.
602 -fn reposition_comment_blank_lines(code: &str) -> String {
603 - let lines: Vec<&str> = code.lines().collect();
604 - if lines.len() < 3 {
605 - return code.to_string();
606 - }
607 -
608 - let mut result: Vec<&str> = Vec::with_capacity(lines.len());
609 - let mut i = 0;
610 -
611 - while i < lines.len() {
612 - // Look for pattern: blank line followed by comment block followed by declaration
613 - if lines[i].trim().is_empty() && i + 1 < lines.len() {
614 - let comment_start = i + 1;
615 - let first_comment = lines[comment_start].trim();
616 -
617 - // Check if the next line is a top-level comment (not indented)
618 - let is_top_level_comment = (first_comment.starts_with("//")
619 - || first_comment.starts_with("/*")
620 - || first_comment.starts_with("/**"))
621 - && !lines[comment_start].starts_with(' ')
622 - && !lines[comment_start].starts_with('\t');
623 -
624 - if is_top_level_comment {
625 - // Find the end of the comment block
626 - let mut comment_end = comment_start;
627 - while comment_end < lines.len() {
628 - let trimmed = lines[comment_end].trim();
629 - if trimmed.starts_with("//")
630 - || trimmed.starts_with("/*")
631 - || trimmed.starts_with("* ")
632 - || trimmed.starts_with("*/")
633 - || trimmed == "*"
634 - || trimmed.starts_with("/**")
635 - {
636 - comment_end += 1;
637 - } else {
638 - break;
639 - }
640 - }
641 -
642 - // Check if the line after the comment block is a top-level
643 - // declaration (function, class, export, const, let, var).
644 - // This is specifically for Babel's codegen which places blank
645 - // lines after comment blocks before declarations, not before.
646 - if comment_end < lines.len() && comment_end > comment_start {
647 - let after_comment = lines[comment_end].trim();
648 - let is_declaration = after_comment.starts_with("function ")
649 - || after_comment.starts_with("export ")
650 - || after_comment.starts_with("class ")
651 - || after_comment.starts_with("const ")
652 - || after_comment.starts_with("let ")
653 - || after_comment.starts_with("var ")
654 - || after_comment.starts_with("import ")
655 - || after_comment.starts_with("async function ")
656 - || after_comment.starts_with("async function*");
657 -
658 - if is_declaration {
659 - // Also check that the line before the blank line is
660 - // non-empty (end of import or end of function)
661 - let prev_non_empty = i > 0 && !lines[i - 1].trim().is_empty();
662 -
663 - if prev_non_empty {
664 - // Move the blank line: emit comment block first,
665 - // then blank line, then continue
666 - for j in comment_start..comment_end {
667 - result.push(lines[j]);
668 - }
669 - result.push(""); // blank line after comments
670 - i = comment_end;
671 - continue;
672 - }
673 - }
674 - }
675 - }
676 - }
677 -
678 - result.push(lines[i]);
679 - i += 1;
680 - }
681 -
682 - // Rejoin, preserving trailing newline if present
683 - let mut output = result.join("\n");
684 - if code.ends_with('\n') && !output.ends_with('\n') {
685 - output.push('\n');
686 - }
687 - output
688 -}
689 -
690 -/// Compute where blank lines should be inserted in the emitted output.
691 -///
692 -/// This replicates Babel's `@babel/generator` behavior: when consecutive
693 -/// top-level items had blank lines between them in the original source,
694 -/// the generator preserves those blank lines.
695 -///
696 -/// We check the item spans (byte positions into the original source) and
697 -/// determine if there was a blank line gap between consecutive items.
698 -/// We also determine WHERE the blank line should go: before the item's
699 -/// leading comments (BeforeItem) or between the comments and code (BeforeCode).
700 -fn compute_blank_line_positions(
701 - body: &[swc_ecma_ast::ModuleItem],
702 - source_text: &str,
703 -) -> Vec<BlankLinePosition> {
704 - use swc_common::Spanned;
705 -
706 - let mut result = Vec::new();
707 -
708 - // Check for blank lines between leading comments and the first
709 - // non-DUMMY item. This handles the case where comments from the
710 - // source (e.g., pragma comments) are attached as leading comments
711 - // to an import, with a blank line gap in the original source.
712 - for item in body {
713 - let lo = item.span().lo;
714 - if lo.is_dummy() {
715 - continue;
716 - }
717 - let lo_u = (lo.0 as usize).saturating_sub(1);
718 - if lo_u > source_text.len() || lo_u == 0 {
719 - break;
720 - }
721 - // Check the source text before this item for comments followed by blank lines
722 - let before = &source_text[..lo_u];
723 - if has_blank_line(before) && (before.contains("//") || before.contains("/*")) {
724 - // There are comments and blank lines before this item.
725 - // Check if the blank line is between the comments and this item
726 - // (i.e., "BeforeCode" pattern)
727 - if !is_blank_line_before_comments(before) {
728 - let first_code_line = get_first_code_line(item);
729 - result.push(BlankLinePosition::BeforeCode { first_code_line });
730 - }
731 - }
732 - break; // Only check the first non-DUMMY item
733 - }
734 -
735 - for i in 1..body.len() {
736 - let prev = &body[i - 1];
737 - let curr = &body[i];
738 -
739 - let prev_hi = prev.span().hi;
740 - let curr_lo = curr.span().lo;
741 -
742 - // Skip items with dummy/synthetic spans (BytePos(0))
743 - if prev_hi.is_dummy() || curr_lo.is_dummy() {
744 - continue;
745 - }
746 -
747 - // SWC BytePos is 1-based (BytePos(0) is DUMMY/reserved). Convert
748 - // to 0-based source text indices by subtracting 1.
749 - let prev_hi_u = (prev_hi.0 as usize).saturating_sub(1);
750 - let curr_lo_u = (curr_lo.0 as usize).saturating_sub(1);
751 -
752 - if prev_hi_u >= curr_lo_u || prev_hi_u > source_text.len() || curr_lo_u > source_text.len() {
753 - continue;
754 - }
755 -
756 - // Check the text between the two items for blank lines.
757 - // Babel's generator preserves blank lines from the original source
758 - // between consecutive top-level items.
759 - let between = &source_text[prev_hi_u..curr_lo_u];
760 - if !has_blank_line(between) {
761 - continue;
762 - }
763 -
764 - // Only preserve blank lines when there are comments between the
765 - // items. This matches Babel's behavior: the TS compiler's
766 - // replaceWith() creates fresh nodes without position info, so
767 - // Babel's generator only sees position gaps when comments with
768 - // original positions are present between items. Without comments,
769 - // the generated code and the next item end up close together,
770 - // so Babel sees no gap and doesn't insert a blank line.
771 - if !between.contains("//") && !between.contains("/*") {
772 - continue;
773 - }
774 -
775 - // Determine the first code line of the current item (emitted
776 - // without comments) for use as a search anchor.
777 - let first_code_line = get_first_code_line(curr);
778 -
779 - // Determine whether blank lines exist before and/or after comments.
780 - let (blank_before, blank_after) = blank_line_positions_around_comments(between);
781 -
782 - if blank_before && blank_after {
783 - // Both: add blank lines before AND after comments
784 - result.push(BlankLinePosition::BeforeItem { first_code_line: first_code_line.clone() });
785 - result.push(BlankLinePosition::BeforeCode { first_code_line });
786 - } else if blank_after {
787 - result.push(BlankLinePosition::BeforeCode { first_code_line });
788 - } else {
789 - // blank_before only, or no specific position → default to BeforeItem
790 - result.push(BlankLinePosition::BeforeItem { first_code_line });
791 - }
792 - }
793 -
794 - result
795 -}
796 -
797 -/// Check if a string contains a blank line (two consecutive newlines
798 -/// with only whitespace between them).
799 -fn has_blank_line(s: &str) -> bool {
800 - let mut prev_newline = false;
801 - for c in s.chars() {
802 - if c == '\n' {
803 - if prev_newline {
804 - return true;
805 - }
806 - prev_newline = true;
807 - } else if c == ' ' || c == '\t' || c == '\r' {
808 - // whitespace between newlines is ok
809 - } else {
810 - prev_newline = false;
811 - }
812 - }
813 - false
814 -}
815 -
816 -/// Determine where blank lines exist relative to comments in the between-text.
817 -///
818 -/// Returns (blank_before_comments, blank_after_comments):
819 -/// - blank_before: there's a blank line before any comment content
820 -/// - blank_after: there's a blank line after comment content
821 -fn blank_line_positions_around_comments(between: &str) -> (bool, bool) {
822 - let mut found_comment = false;
823 - let mut prev_newline = false;
824 - let mut blank_before = false;
825 - let mut blank_after = false;
826 -
827 - for (i, c) in between.char_indices() {
828 - if c == '\n' {
829 - if prev_newline {
830 - if found_comment {
831 - blank_after = true;
832 - } else {
833 - blank_before = true;
834 - }
835 - }
836 - prev_newline = true;
837 - } else if c == ' ' || c == '\t' || c == '\r' {
838 - // whitespace between newlines is ok
839 - } else {
840 - prev_newline = false;
841 - if c == '/' {
842 - let next = between.as_bytes().get(i + 1);
843 - if next == Some(&b'*') || next == Some(&b'/') {
844 - found_comment = true;
845 - }
846 - }
847 - }
848 - }
849 -
850 - (blank_before, blank_after)
851 -}
852 -
853 -/// Check if the blank line in the between-text should be placed before
854 -/// comments. Used for the first-item leading comment check.
855 -fn is_blank_line_before_comments(between: &str) -> bool {
856 - let (blank_before, blank_after) = blank_line_positions_around_comments(between);
857 - // If blank lines exist after comments, prefer BeforeCode (return false)
858 - if blank_after {
859 - return false;
860 - }
861 - blank_before
862 -}
863 -
864 -/// Get the first non-empty line of a ModuleItem when emitted without
865 -/// comments. Goes through `emit_module_to_string` so the text matches the
866 -/// final emitted output (including the namespace-export fixup).
867 -fn get_first_code_line(item: &swc_ecma_ast::ModuleItem) -> String {
868 - let single_module = swc_ecma_ast::Module {
869 - span: swc_common::DUMMY_SP,
870 - body: vec![item.clone()],
871 - shebang: None,
872 - };
873 -
874 - let code = emit_module_to_string(&single_module, None);
875 - code.lines()
876 - .find(|l| !l.trim().is_empty())
877 - .unwrap_or("")
878 - .to_string()
879 -}
880 -
881 -/// Extract comments from source text using SWC's parser.
882 -/// Returns a list of (BytePos, Vec<Comment>) pairs where the BytePos is the
883 -/// position of the token following the comment(s).
884 -fn extract_source_comments(
885 - source_text: &str,
886 -) -> (
887 - Vec<(swc_common::BytePos, Vec<swc_common::comments::Comment>)>,
888 - Vec<(swc_common::BytePos, Vec<swc_common::comments::Comment>)>,
889 -) {
890 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
891 - let fm = cm.new_source_file(
892 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
893 - source_text.to_string(),
894 - );
895 -
896 - let comments = swc_common::comments::SingleThreadedComments::default();
897 - let mut errors = vec![];
898 - // Try parsing as JSX+TS to handle maximum syntax variety
899 - let _ = swc_ecma_parser::parse_file_as_module(
900 - &fm,
901 - swc_ecma_parser::Syntax::Typescript(swc_ecma_parser::TsSyntax {
902 - tsx: true,
903 - ..Default::default()
904 - }),
905 - swc_ecma_ast::EsVersion::latest(),
906 - Some(&comments),
907 - &mut errors,
908 - );
909 -
910 - let mut leading_result = Vec::new();
911 - let mut trailing_result = Vec::new();
912 - let (leading, trailing) = comments.borrow_all();
913 - for (pos, cmts) in leading.iter() {
914 - if !cmts.is_empty() {
915 - leading_result.push((*pos, cmts.clone()));
916 - }
917 - }
918 - for (pos, cmts) in trailing.iter() {
919 - if !cmts.is_empty() {
920 - trailing_result.push((*pos, cmts.clone()));
921 - }
922 - }
923 -
924 - (leading_result, trailing_result)
925 -}
926 -
927 -/// Normalize source code formatting to match Babel's codegen behavior.
928 -/// Applied to source text that was not modified by the compiler.
929 -/// Currently adds blank lines after directive sequences, matching
930 -/// Babel's generator which always emits a blank line after the last
931 -/// directive in a function/program body.
932 -pub fn normalize_source(source: &str) -> String {
933 - let code = add_blank_lines_after_directives(source);
934 - let code = remove_blank_lines_after_last_import(&code);
935 - let code = remove_blank_lines_before_fixture_entrypoint(&code);
936 - expand_fixture_entrypoint_objects(&code)
937 -}
938 -
939 -/// Remove blank lines immediately before `export const FIXTURE_ENTRYPOINT`.
940 -/// Babel's codegen doesn't preserve blank lines between function declarations
941 -/// and the FIXTURE_ENTRYPOINT export.
942 -fn remove_blank_lines_before_fixture_entrypoint(code: &str) -> String {
943 - let lines: Vec<&str> = code.lines().collect();
944 - if lines.is_empty() {
945 - return code.to_string();
946 - }
947 -
948 - // Find the FIXTURE_ENTRYPOINT line
949 - let mut entrypoint_idx: Option<usize> = None;
950 - for (i, &line) in lines.iter().enumerate() {
951 - if line.trim().starts_with("export const FIXTURE_ENTRYPOINT")
952 - || line.trim().starts_with("export const FIXTURE_ENTRYPOINT")
953 - {
954 - entrypoint_idx = Some(i);
955 - break;
956 - }
957 - }
958 -
959 - let entrypoint_idx = match entrypoint_idx {
960 - Some(idx) if idx > 0 => idx,
961 - _ => return code.to_string(),
962 - };
963 -
964 - // Check if the line before FIXTURE_ENTRYPOINT is blank
965 - if !lines[entrypoint_idx - 1].trim().is_empty() {
966 - return code.to_string();
967 - }
968 -
969 - // Remove the blank line
970 - let mut result: Vec<&str> = Vec::with_capacity(lines.len());
971 - for (i, &line) in lines.iter().enumerate() {
972 - if i == entrypoint_idx - 1 {
973 - continue;
974 - }
975 - result.push(line);
976 - }
977 -
978 - let mut output = result.join("\n");
979 - if code.ends_with('\n') && !output.ends_with('\n') {
980 - output.push('\n');
981 - }
982 - output
983 -}
984 -
985 -/// Remove blank lines between the last import declaration and the first
986 -/// non-import statement. Babel's codegen doesn't preserve these blank lines.
987 -///
988 -/// Only removes blank lines that immediately follow the LAST import line
989 -/// (not blank lines between comments or between import groups).
990 -fn remove_blank_lines_after_last_import(code: &str) -> String {
991 - let lines: Vec<&str> = code.lines().collect();
992 - if lines.is_empty() {
993 - return code.to_string();
994 - }
995 -
996 - // Find the index of the last import statement
997 - let mut last_import_idx: Option<usize> = None;
998 - for (i, &line) in lines.iter().enumerate() {
999 - let trimmed = line.trim();
1000 - if trimmed.starts_with("import ") || trimmed.starts_with("import{") {
1001 - last_import_idx = Some(i);
1002 - }
1003 - }
1004 -
1005 - let last_import_idx = match last_import_idx {
1006 - Some(idx) => idx,
1007 - None => return code.to_string(),
1008 - };
1009 -
1010 - // Check if there's a blank line immediately after the last import
1011 - let blank_idx = last_import_idx + 1;
1012 - if blank_idx >= lines.len() || !lines[blank_idx].trim().is_empty() {
1013 - return code.to_string();
1014 - }
1015 -
1016 - // Remove this blank line
1017 - let mut result: Vec<&str> = Vec::with_capacity(lines.len());
1018 - for (i, &line) in lines.iter().enumerate() {
1019 - if i == blank_idx {
1020 - continue; // skip the blank line
1021 - }
1022 - result.push(line);
1023 - }
1024 -
1025 - let mut output = result.join("\n");
1026 - if code.ends_with('\n') && !output.ends_with('\n') {
1027 - output.push('\n');
1028 - }
1029 - output
1030 -}
1031 -
1032 -/// Expand single-line object literals to multi-line format within
1033 -/// FIXTURE_ENTRYPOINT structures only.
1034 -///
1035 -/// SWC's codegen emits small objects on a single line (e.g.,
1036 -/// `params: [{ value: "test" }]`), while Babel's codegen puts them on
1037 -/// multiple lines. Since prettier preserves the single-line vs multi-line
1038 -/// choice, we need to expand them before prettier runs.
1039 -///
1040 -/// This function ONLY operates within FIXTURE_ENTRYPOINT blocks to avoid
1041 -/// affecting compiled code.
1042 -fn expand_fixture_entrypoint_objects(code: &str) -> String {
1043 - // Find the start of FIXTURE_ENTRYPOINT block
1044 - let entrypoint_marker = "FIXTURE_ENTRYPOINT";
1045 - if !code.contains(entrypoint_marker) {
1046 - return code.to_string();
1047 - }
1048 -
1049 - // Find the byte position of FIXTURE_ENTRYPOINT
1050 - let entrypoint_pos = match code.find(entrypoint_marker) {
1051 - Some(pos) => pos,
1052 - None => return code.to_string(),
1053 - };
1054 -
1055 - // Only process lines after FIXTURE_ENTRYPOINT
1056 - let (before, after) = code.split_at(entrypoint_pos);
1057 - let expanded = expand_single_line_objects_in_block(after);
1058 - format!("{}{}", before, expanded)
1059 -}
1060 -
1061 -fn expand_single_line_objects_in_block(code: &str) -> String {
1062 - let mut result = String::with_capacity(code.len() + 256);
1063 - let lines: Vec<&str> = code.lines().collect();
1064 -
1065 - for (idx, &line) in lines.iter().enumerate() {
1066 - if let Some(expanded) = try_expand_object_line(line) {
1067 - result.push_str(&expanded);
1068 - } else {
1069 - result.push_str(line);
1070 - }
1071 - if idx < lines.len() - 1 || code.ends_with('\n') {
1072 - result.push('\n');
1073 - }
1074 - }
1075 -
1076 - result
1077 -}
1078 -
1079 -/// Try to expand a single-line object literal to multi-line.
1080 -/// Returns Some(expanded) if the line contains an expandable object, None otherwise.
1081 -fn try_expand_object_line(line: &str) -> Option<String> {
1082 - let trimmed = line.trim();
1083 -
1084 - // Calculate indentation
1085 - let indent = &line[..line.len() - line.trim_start().len()];
1086 -
1087 - // Pattern 1: `key: [{ prop: val, prop2: val2 }],` or `key: [{ ... }, { ... }],`
1088 - // Pattern 2: `[{ prop: val }, { prop: val }]` (array of objects)
1089 - // We need to find `[` containing `{...}` entries
1090 -
1091 - // Check if this line has a [ ... ] with { ... } objects inside
1092 - if !trimmed.contains("[{") && !trimmed.contains("{ ") {
1093 - return None;
1094 - }
1095 -
1096 - // Find the bracket-enclosed array content
1097 - let bracket_start = trimmed.find('[')?;
1098 - let bracket_end = trimmed.rfind(']')?;
1099 - if bracket_start >= bracket_end {
1100 - return None;
1101 - }
1102 -
1103 - let array_content = &trimmed[bracket_start + 1..bracket_end];
1104 - let inner_trimmed = array_content.trim();
1105 -
1106 - // Check if this contains objects: at least one `{ ... }`
1107 - if !inner_trimmed.starts_with('{') || !inner_trimmed.contains(':') {
1108 - return None;
1109 - }
1110 -
1111 - // We need at least one property with a colon to expand
1112 - if !inner_trimmed.contains(':') {
1113 - return None;
1114 - }
1115 -
1116 - // Split the array content into individual elements
1117 - let prefix = &trimmed[..bracket_start + 1];
1118 - let suffix = &trimmed[bracket_end..];
1119 -
1120 - // Parse the objects - split at `}, {` boundaries
1121 - let elements = split_array_elements(inner_trimmed);
1122 -
1123 - let inner_indent = format!("{} ", indent);
1124 - let prop_indent = format!("{} ", indent);
1125 -
1126 - let mut result = String::new();
1127 - result.push_str(indent);
1128 - result.push_str(prefix);
1129 - result.push('\n');
1130 -
1131 - for (i, elem) in elements.iter().enumerate() {
1132 - let elem = elem.trim();
1133 - if elem.starts_with('{') && elem.ends_with('}') {
1134 - // Expand this object
1135 - let obj_content = &elem[1..elem.len() - 1].trim();
1136 - let props = split_object_properties(obj_content);
1137 -
1138 - result.push_str(&inner_indent);
1139 - result.push_str("{\n");
1140 - for (_j, prop) in props.iter().enumerate() {
1141 - result.push_str(&prop_indent);
1142 - result.push_str(prop.trim());
1143 - result.push_str(",\n");
1144 - }
1145 - result.push_str(&inner_indent);
1146 - result.push('}');
1147 - } else {
1148 - result.push_str(&inner_indent);
1149 - result.push_str(elem);
1150 - }
1151 - if i < elements.len() - 1 {
1152 - result.push(',');
1153 - }
1154 - result.push('\n');
1155 - }
1156 -
1157 - result.push_str(indent);
1158 - result.push_str(suffix);
1159 -
1160 - Some(result)
1161 -}
1162 -
1163 -/// Split array content into individual elements, respecting nested braces/brackets.
1164 -fn split_array_elements(s: &str) -> Vec<String> {
1165 - let mut elements = Vec::new();
1166 - let mut current = String::new();
1167 - let mut depth = 0;
1168 -
1169 - for ch in s.chars() {
1170 - match ch {
1171 - '{' | '[' | '(' => {
1172 - depth += 1;
1173 - current.push(ch);
1174 - }
1175 - '}' | ']' | ')' => {
1176 - depth -= 1;
1177 - current.push(ch);
1178 - }
1179 - ',' if depth == 0 => {
1180 - let trimmed = current.trim().to_string();
1181 - if !trimmed.is_empty() {
1182 - elements.push(trimmed);
1183 - }
1184 - current.clear();
1185 - }
1186 - _ => {
1187 - current.push(ch);
1188 - }
1189 - }
1190 - }
1191 - let trimmed = current.trim().to_string();
1192 - if !trimmed.is_empty() {
1193 - elements.push(trimmed);
1194 - }
1195 - elements
1196 -}
1197 -
1198 -/// Split object properties, respecting nested structures.
1199 -fn split_object_properties(s: &str) -> Vec<String> {
1200 - let mut props = Vec::new();
1201 - let mut current = String::new();
1202 - let mut depth = 0;
1203 -
1204 - for ch in s.chars() {
1205 - match ch {
1206 - '{' | '[' | '(' => {
1207 - depth += 1;
1208 - current.push(ch);
1209 - }
1210 - '}' | ']' | ')' => {
1211 - depth -= 1;
1212 - current.push(ch);
1213 - }
1214 - ',' if depth == 0 => {
1215 - let trimmed = current.trim().to_string();
1216 - if !trimmed.is_empty() {
1217 - props.push(trimmed);
1218 - }
1219 - current.clear();
1220 - }
1221 - _ => {
1222 - current.push(ch);
1223 - }
1224 - }
1225 - }
1226 - let trimmed = current.trim().to_string();
1227 - if !trimmed.is_empty() {
1228 - props.push(trimmed);
1229 - }
1230 - props
1231 -}
1232 -
1233 -/// Convenience wrapper — parses source text, then lints.
1234 -pub fn lint_source(source_text: &str, options: PluginOptions) -> LintResult {
1235 - let cm = swc_common::sync::Lrc::new(swc_common::SourceMap::default());
1236 - let fm = cm.new_source_file(
1237 - swc_common::sync::Lrc::new(swc_common::FileName::Anon),
1238 - source_text.to_string(),
1239 - );
1240 -
1241 - let mut errors = vec![];
1242 - let module = swc_ecma_parser::parse_file_as_module(
1243 - &fm,
1244 - swc_ecma_parser::Syntax::Es(swc_ecma_parser::EsSyntax {
1245 - jsx: true,
1246 - ..Default::default()
1247 - }),
1248 - swc_ecma_ast::EsVersion::latest(),
1249 - None,
1250 - &mut errors,
1251 - );
1252 -
1253 - match module {
1254 - Ok(module) => lint(&module, source_text, options),
1255 - Err(_) => LintResult {
1256 - diagnostics: vec![],
1257 - },
1258 - }
1259 -}
compiler/crates/react_compiler_swc/src/prefilter.rs deleted
-205
@@ -1,205 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use swc_ecma_ast::{
7 - ArrowExpr, AssignExpr, AssignTarget, Callee, Class, Expr, FnDecl, FnExpr, MemberProp,
8 - Module, Pat, SimpleAssignTarget, VarDeclarator, CallExpr,
9 -};
10 -use swc_ecma_visit::Visit;
11 -
12 -/// Checks if a module contains React-like functions (components or hooks).
13 -///
14 -/// A React-like function is one whose name:
15 -/// - Starts with an uppercase letter (component convention)
16 -/// - Matches the pattern `use[A-Z0-9]` (hook convention)
17 -pub fn has_react_like_functions(module: &Module) -> bool {
18 - let mut visitor = ReactLikeVisitor {
19 - found: false,
20 - current_name: None,
21 - };
22 - visitor.visit_module(module);
23 - visitor.found
24 -}
25 -
26 -use react_compiler_hir::environment::is_react_like_name;
27 -
28 -struct ReactLikeVisitor {
29 - found: bool,
30 - current_name: Option<String>,
31 -}
32 -
33 -impl Visit for ReactLikeVisitor {
34 - fn visit_var_declarator(&mut self, decl: &VarDeclarator) {
35 - if self.found {
36 - return;
37 - }
38 -
39 - // Extract name from the binding identifier
40 - let name = match &decl.name {
41 - Pat::Ident(binding_ident) => Some(binding_ident.id.sym.to_string()),
42 - _ => None,
43 - };
44 -
45 - let prev_name = self.current_name.take();
46 - self.current_name = name;
47 -
48 - // Visit the initializer with the name in scope
49 - if let Some(init) = &decl.init {
50 - self.visit_expr(init);
51 - }
52 -
53 - self.current_name = prev_name;
54 - }
55 -
56 - fn visit_assign_expr(&mut self, expr: &AssignExpr) {
57 - if self.found {
58 - return;
59 - }
60 -
61 - let name = match &expr.left {
62 - AssignTarget::Simple(SimpleAssignTarget::Ident(binding_ident)) => {
63 - Some(binding_ident.id.sym.to_string())
64 - }
65 - _ => None,
66 - };
67 -
68 - let prev_name = self.current_name.take();
69 - self.current_name = name;
70 -
71 - self.visit_expr(&expr.right);
72 -
73 - self.current_name = prev_name;
74 - }
75 -
76 - fn visit_fn_decl(&mut self, decl: &FnDecl) {
77 - if self.found {
78 - return;
79 - }
80 -
81 - if is_react_like_name(&decl.ident.sym) {
82 - self.found = true;
83 - return;
84 - }
85 -
86 - // Don't traverse into the function body
87 - }
88 -
89 - fn visit_fn_expr(&mut self, expr: &FnExpr) {
90 - if self.found {
91 - return;
92 - }
93 -
94 - // Check explicit function name
95 - if let Some(id) = &expr.ident {
96 - if is_react_like_name(&id.sym) {
97 - self.found = true;
98 - return;
99 - }
100 - }
101 -
102 - // Check inferred name from parent context
103 - if expr.ident.is_none() {
104 - if let Some(name) = &self.current_name {
105 - if is_react_like_name(name) {
106 - self.found = true;
107 - return;
108 - }
109 - }
110 - }
111 -
112 - // Don't traverse into the function body
113 - }
114 -
115 - fn visit_arrow_expr(&mut self, _expr: &ArrowExpr) {
116 - if self.found {
117 - return;
118 - }
119 -
120 - if let Some(name) = &self.current_name {
121 - if is_react_like_name(name) {
122 - self.found = true;
123 - return;
124 - }
125 - }
126 -
127 - // Don't traverse into the function body
128 - }
129 -
130 - fn visit_call_expr(&mut self, call: &CallExpr) {
131 - if self.found {
132 - return;
133 - }
134 -
135 - // Check if this is memo(fn) / forwardRef(fn) / React.memo(fn) / React.forwardRef(fn)
136 - if is_memo_or_forward_ref_call(call) {
137 - // If the first arg is a function expression or arrow, mark as found
138 - if let Some(first_arg) = call.args.first() {
139 - match &*first_arg.expr {
140 - Expr::Fn(_) | Expr::Arrow(_) => {
141 - self.found = true;
142 - return;
143 - }
144 - _ => {}
145 - }
146 - }
147 - }
148 -
149 - // Continue traversal for other call expressions
150 - // Visit args to find function expressions inside non-memo/forwardRef calls
151 - for arg in &call.args {
152 - self.visit_expr(&arg.expr);
153 - }
154 - }
155 -
156 - fn visit_class(&mut self, _class: &Class) {
157 - // Skip class bodies entirely
158 - }
159 -}
160 -
161 -fn is_memo_or_forward_ref_call(call: &CallExpr) -> bool {
162 - match &call.callee {
163 - Callee::Expr(expr) => match &**expr {
164 - // Direct calls: memo(...) or forwardRef(...)
165 - Expr::Ident(ident) => {
166 - ident.sym == "memo" || ident.sym == "forwardRef"
167 - }
168 - // Member expression: React.memo(...) or React.forwardRef(...)
169 - Expr::Member(member) => {
170 - if let Expr::Ident(obj) = &*member.obj {
171 - if obj.sym == "React" {
172 - if let MemberProp::Ident(prop) = &member.prop {
173 - return prop.sym == "memo" || prop.sym == "forwardRef";
174 - }
175 - }
176 - }
177 - false
178 - }
179 - _ => false,
180 - },
181 - _ => false,
182 - }
183 -}
184 -
185 -#[cfg(test)]
186 -mod tests {
187 - use super::*;
188 -
189 - #[test]
190 - fn test_is_react_like_name() {
191 - assert!(is_react_like_name("Component"));
192 - assert!(is_react_like_name("MyComponent"));
193 - assert!(is_react_like_name("A"));
194 - assert!(is_react_like_name("useState"));
195 - assert!(is_react_like_name("useEffect"));
196 - assert!(is_react_like_name("use0"));
197 -
198 - assert!(!is_react_like_name("component"));
199 - assert!(!is_react_like_name("myFunction"));
200 - assert!(!is_react_like_name("use"));
201 - assert!(!is_react_like_name("user"));
202 - assert!(!is_react_like_name("useful"));
203 - assert!(!is_react_like_name(""));
204 - }
205 -}
compiler/crates/react_compiler_swc/src/ts_namespace_export_fixup.rs deleted
-390
@@ -1,390 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -//! Workaround for an upstream swc_ecma_codegen bug: `TsNamespaceExportDecl`
7 -//! prints as the `TsExportAssignment` shape.
8 -//!
9 -//! In swc_ecma_codegen v24 (`crates/swc_ecma_codegen/src/typescript.rs`),
10 -//! `TsNamespaceExportDecl::emit` writes `export`, `=`, then the identifier —
11 -//! producing `export = Foo` instead of `export as namespace Foo`. The bug is
12 -//! also present on swc master as of June 2026; no upstream issue has been
13 -//! filed yet.
14 -//!
15 -//! Removal criterion: delete this module and its call site in
16 -//! `emit_module_to_string` when [`tests::upstream_bug_guard`] fails after an
17 -//! swc upgrade (a failure means swc now prints `export as namespace`).
18 -//!
19 -//! # How the rewrite is anchored
20 -//!
21 -//! A genuine `export = Foo` assignment prints the exact same text as the
22 -//! broken namespace-export print, so the affected lines cannot be identified
23 -//! from the output text alone. Instead the emit path records a source map
24 -//! (`Vec<(BytePos, LineCol)>` via `JsWriter`), which attributes output
25 -//! positions to input spans, and the fixup resolves each top-level
26 -//! `TsNamespaceExportDecl` to its output line through it.
27 -//!
28 -//! Empirically (pinned by [`tests::srcmap_records_id_position_within_span`]),
29 -//! the v24 emitter records no entry for the declaration's own `span.lo` —
30 -//! the only position recorded while emitting the node is the identifier's
31 -//! `span.lo`, which lies inside `[span.lo, span.hi)` and maps to the broken
32 -//! line. Candidate entries are therefore collected over that whole span
33 -//! range (exact `span.lo` matches first, then ascending position) and the
34 -//! first candidate line whose content verifies as `export = {id}` (optional
35 -//! trailing `;` / whitespace) is rewritten.
36 -//!
37 -//! Content verification acts as a candidate *filter*, not a post-hoc
38 -//! assertion: compiler-generated items receive synthetic spans starting at
39 -//! `BytePos(1)` (see `transform`), so when the namespace export is the first
40 -//! source statement its `span.lo` collides with a synthetic span and the
41 -//! exact-match candidate points at the generated import's line. The id-span
42 -//! candidate still verifies, so the scan settles on the correct line.
43 -//!
44 -//! If no candidate verifies — or the declaration has a dummy span, so the
45 -//! emitter recorded nothing for it — the fixup panics. A silent fallback
46 -//! would emit `export = Foo`, which has different semantics (an export
47 -//! assignment), and this is exactly the silently-wrong path the previous
48 -//! text-pairing implementation could take when its line matching drifted.
49 -
50 -use swc_common::BytePos;
51 -use swc_common::LineCol;
52 -use swc_ecma_ast::ModuleDecl;
53 -use swc_ecma_ast::ModuleItem;
54 -use swc_ecma_ast::TsNamespaceExportDecl;
55 -
56 -/// Rewrite the output lines of top-level `TsNamespaceExportDecl` items from
57 -/// the broken `export = {id}` print to `export as namespace {id};`.
58 -///
59 -/// `code` must be the raw emitter output (before any other post-emit text
60 -/// fixups) and `srcmap` the source map recorded while emitting it; line
61 -/// numbers in the source map index directly into `code`'s lines.
62 -pub(crate) fn fix_ts_namespace_export_decls(
63 - body: &[ModuleItem],
64 - code: &str,
65 - srcmap: &[(BytePos, LineCol)],
66 -) -> String {
67 - let decls: Vec<&TsNamespaceExportDecl> = body
68 - .iter()
69 - .filter_map(|item| match item {
70 - ModuleItem::ModuleDecl(ModuleDecl::TsNamespaceExport(d)) => Some(d),
71 - _ => None,
72 - })
73 - .collect();
74 -
75 - if decls.is_empty() {
76 - return code.to_string();
77 - }
78 -
79 - let mut lines: Vec<String> = code.lines().map(str::to_string).collect();
80 - for decl in decls {
81 - let id = decl.id.sym.to_string();
82 - let line_idx = locate_broken_line(decl, &id, &lines, srcmap);
83 - lines[line_idx] = format!("export as namespace {id};");
84 - }
85 -
86 - let mut output = lines.join("\n");
87 - if code.ends_with('\n') {
88 - output.push('\n');
89 - }
90 - output
91 -}
92 -
93 -/// Find the output line holding the broken `export = {id}` print of `decl`.
94 -///
95 -/// Candidates are the source-map entries within `[span.lo, span.hi)`,
96 -/// ordered exact-`span.lo` matches first and then by ascending position
97 -/// (stable, so emission order breaks ties). The first candidate line whose
98 -/// content verifies is returned. Panics — deliberately, see the module docs
99 -/// — when the declaration cannot be located.
100 -fn locate_broken_line(
101 - decl: &TsNamespaceExportDecl,
102 - id: &str,
103 - lines: &[String],
104 - srcmap: &[(BytePos, LineCol)],
105 -) -> usize {
106 - if decl.span.lo.is_dummy() {
107 - panic!(
108 - "[react-compiler] internal error: cannot rewrite `export as namespace {id}`: \
109 - the declaration has a dummy span, so the emitter recorded no source-map \
110 - positions for it"
111 - );
112 - }
113 -
114 - let mut candidates: Vec<(BytePos, LineCol)> = srcmap
115 - .iter()
116 - .filter(|(pos, _)| decl.span.lo <= *pos && *pos < decl.span.hi)
117 - .copied()
118 - .collect();
119 - candidates.sort_by_key(|(pos, _)| (*pos != decl.span.lo, *pos));
120 -
121 - let mut seen_lines: Vec<usize> = Vec::new();
122 - for (_, loc) in &candidates {
123 - let line_idx = loc.line as usize;
124 - if seen_lines.contains(&line_idx) {
125 - continue;
126 - }
127 - seen_lines.push(line_idx);
128 - if let Some(line) = lines.get(line_idx) {
129 - if is_broken_namespace_export_line(line, id) {
130 - return line_idx;
131 - }
132 - }
133 - }
134 -
135 - let candidate_lines: Vec<&str> = seen_lines
136 - .iter()
137 - .filter_map(|&idx| lines.get(idx).map(String::as_str))
138 - .collect();
139 - panic!(
140 - "[react-compiler] internal error: cannot rewrite `export as namespace {id}`: no \
141 - source-map position within its span ({}..{}) maps to an `export = {id}` output \
142 - line (candidate lines: {candidate_lines:?})",
143 - decl.span.lo.0, decl.span.hi.0
144 - );
145 -}
146 -
147 -/// Whether `line` is the broken namespace-export print for `id`: exactly
148 -/// `export = {id}`, allowing an optional trailing `;` and trailing
149 -/// whitespace. (The v24 emitter writes no semicolon after this node, but a
150 -/// future swc may add one before fixing the print itself.)
151 -fn is_broken_namespace_export_line(line: &str, id: &str) -> bool {
152 - let line = line.trim_end();
153 - let line = line.strip_suffix(';').unwrap_or(line);
154 - match line.strip_prefix("export = ") {
155 - Some(rest) => rest == id,
156 - None => false,
157 - }
158 -}
159 -
160 -#[cfg(test)]
161 -mod tests {
162 - use swc_common::BytePos;
163 - use swc_common::FileName;
164 - use swc_common::LineCol;
165 - use swc_common::SourceMap;
166 - use swc_common::Spanned;
167 - use swc_common::comments::SingleThreadedComments;
168 - use swc_common::sync::Lrc;
169 - use swc_ecma_ast::EsVersion;
170 - use swc_ecma_parser::Syntax;
171 - use swc_ecma_parser::parse_file_as_module;
172 -
173 - fn parse(source: &str) -> swc_ecma_ast::Module {
174 - parse_with_comments(source, None)
175 - }
176 -
177 - fn parse_with_comments(
178 - source: &str,
179 - comments: Option<&SingleThreadedComments>,
180 - ) -> swc_ecma_ast::Module {
181 - let cm = Lrc::new(SourceMap::default());
182 - let fm = cm.new_source_file(Lrc::new(FileName::Anon), source.to_string());
183 - let mut errors = vec![];
184 - parse_file_as_module(
185 - &fm,
186 - Syntax::Typescript(swc_ecma_parser::TsSyntax {
187 - tsx: true,
188 - ..Default::default()
189 - }),
190 - EsVersion::latest(),
191 - comments.map(|c| c as &dyn swc_common::comments::Comments),
192 - &mut errors,
193 - )
194 - .expect("Failed to parse")
195 - }
196 -
197 - /// Plain swc_ecma_codegen emit, WITHOUT the fixup, collecting the
198 - /// source map.
199 - fn raw_emit_with_srcmap(module: &swc_ecma_ast::Module) -> (String, Vec<(BytePos, LineCol)>) {
200 - let cm = Lrc::new(SourceMap::default());
201 - let mut buf = vec![];
202 - let mut srcmap: Vec<(BytePos, LineCol)> = Vec::new();
203 - {
204 - let wr = swc_ecma_codegen::text_writer::JsWriter::new(
205 - cm.clone(),
206 - "\n",
207 - &mut buf,
208 - Some(&mut srcmap),
209 - );
210 - let mut emitter = swc_ecma_codegen::Emitter {
211 - cfg: swc_ecma_codegen::Config::default().with_minify(false),
212 - cm,
213 - comments: None,
214 - wr: Box::new(wr),
215 - };
216 - swc_ecma_codegen::Node::emit_with(module, &mut emitter).unwrap();
217 - }
218 - (String::from_utf8(buf).unwrap(), srcmap)
219 - }
220 -
221 - /// Emit through the crate's fixed emit path (fixup applied).
222 - fn fixed_emit(source: &str) -> String {
223 - let module = parse(source);
224 - crate::emit_module_to_string(&module, None)
225 - }
226 -
227 - /// Guard test for the upstream bug. When this test FAILS, swc has fixed
228 - /// `TsNamespaceExportDecl::emit`: delete this module and its call site
229 - /// in `emit_module_to_string`.
230 - #[test]
231 - fn upstream_bug_guard() {
232 - let module = parse("export as namespace Foo;\n");
233 - let (code, _) = raw_emit_with_srcmap(&module);
234 - assert!(
235 - code.contains("export = "),
236 - "expected the upstream TsNamespaceExportDecl bug (`export = `): {code:?}"
237 - );
238 - assert!(
239 - !code.contains("export as namespace"),
240 - "swc now prints `export as namespace`: the upstream bug is fixed, delete this \
241 - module: {code:?}"
242 - );
243 - }
244 -
245 - /// Pins the source-map invariant the fixup depends on: emitting a
246 - /// `TsNamespaceExportDecl` records at least one position within the
247 - /// declaration's span, and that position maps to the broken output
248 - /// line. Empirically in v24 the recorded position is the identifier's
249 - /// `span.lo` (the declaration's own `span.lo` is never recorded).
250 - #[test]
251 - fn srcmap_records_id_position_within_span() {
252 - let source = "const x = 1;\nexport as namespace Foo;\n";
253 - let module = parse(source);
254 - let decl_span = module.body[1].span();
255 - let id_span = match &module.body[1] {
256 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsNamespaceExport(
257 - d,
258 - )) => d.id.span,
259 - other => panic!("expected TsNamespaceExport, got {other:?}"),
260 - };
261 -
262 - let (code, srcmap) = raw_emit_with_srcmap(&module);
263 - let in_range: Vec<&(BytePos, LineCol)> = srcmap
264 - .iter()
265 - .filter(|(pos, _)| decl_span.lo <= *pos && *pos < decl_span.hi)
266 - .collect();
267 -
268 - assert!(
269 - !in_range.is_empty(),
270 - "no srcmap position recorded within the decl span; the srcmap-anchored fixup \
271 - design no longer works: {srcmap:?}"
272 - );
273 - // The identifier's lo is the recorded anchor...
274 - assert_eq!(in_range[0].0, id_span.lo);
275 - // ...and it maps to the broken `export = Foo` line.
276 - let line = code.lines().nth(in_range[0].1.line as usize).unwrap();
277 - assert_eq!(line, "export = Foo");
278 - // The declaration's own span.lo is not recorded by the v24 emitter.
279 - assert!(
280 - !srcmap.iter().any(|(pos, _)| *pos == decl_span.lo),
281 - "v24 unexpectedly records the decl's span.lo; revisit candidate ordering: \
282 - {srcmap:?}"
283 - );
284 - }
285 -
286 - /// The emitter prints the genuine assignment as `export = lib` (its
287 - /// pending semicolon is dropped before the newline), byte-identical in
288 - /// shape to the broken namespace print, so only the srcmap tells them
289 - /// apart.
290 - #[test]
291 - fn umd_pair_rewrites_only_the_namespace_export() {
292 - assert_eq!(
293 - fixed_emit("export = lib;\nexport as namespace Foo;\n"),
294 - "export = lib\nexport as namespace Foo;\n"
295 - );
296 - }
297 -
298 - #[test]
299 - fn two_namespace_exports() {
300 - assert_eq!(
301 - fixed_emit("export as namespace A;\nexport as namespace B;\n"),
302 - "export as namespace A;\nexport as namespace B;\n"
303 - );
304 - }
305 -
306 - /// The decoy's span lies outside the declaration's span, so it is never
307 - /// a rewrite candidate.
308 - #[test]
309 - fn template_literal_decoy_is_not_rewritten() {
310 - let source = "const s = `\nexport = Foo\n`;\nexport as namespace Foo;\n";
311 - assert_eq!(
312 - fixed_emit(source),
313 - "const s = `\nexport = Foo\n`;\nexport as namespace Foo;\n"
314 - );
315 - }
316 -
317 - /// The expected output keeps `*/ const x = 1;` on one line: the raw
318 - /// emitter glues code after a block comment, and the later
319 - /// `fix_block_comment_newlines` pass (not under test here) splits it.
320 - #[test]
321 - fn block_comment_decoy_is_not_rewritten() {
322 - let source = "/*\nexport = Foo\n*/\nconst x = 1;\nexport as namespace Foo;\n";
323 - let comments = SingleThreadedComments::default();
324 - let module = parse_with_comments(source, Some(&comments));
325 - let code = crate::emit_module_to_string(&module, Some(&comments));
326 - assert_eq!(
327 - code,
328 - "/*\nexport = Foo\n*/ const x = 1;\nexport as namespace Foo;\n"
329 - );
330 - }
331 -
332 - #[test]
333 - fn parenthesized_export_assignment_is_untouched() {
334 - assert_eq!(
335 - fixed_emit("export = (Foo);\nexport as namespace Foo;\n"),
336 - "export = (Foo)\nexport as namespace Foo;\n"
337 - );
338 - }
339 -
340 - /// The real-pipeline collision shape: the namespace export is the first
341 - /// source statement (span.lo == BytePos(1)) and a compiler-generated
342 - /// import carries the synthetic span BytePos(1) (see `transform`). The
343 - /// exact-`span.lo` candidate points at the import's line; content
344 - /// verification filters it out and the id-span candidate wins.
345 - #[test]
346 - fn synthetic_span_collision_resolves_to_the_real_line() {
347 - let source = "export as namespace Foo;\nfunction f() {}\n";
348 - let mut module = parse(source);
349 - let synthetic_span = swc_common::Span::new(BytePos(1), BytePos(1));
350 - module.body.insert(
351 - 0,
352 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::Import(
353 - swc_ecma_ast::ImportDecl {
354 - span: synthetic_span,
355 - specifiers: vec![],
356 - src: Box::new(swc_ecma_ast::Str {
357 - span: swc_common::DUMMY_SP,
358 - value: "react/compiler-runtime".into(),
359 - raw: None,
360 - }),
361 - type_only: false,
362 - with: None,
363 - phase: Default::default(),
364 - },
365 - )),
366 - );
367 -
368 - let code = crate::emit_module_to_string(&module, None);
369 - assert_eq!(
370 - code,
371 - "import \"react/compiler-runtime\";\nexport as namespace Foo;\nfunction f() {}\n"
372 - );
373 - }
374 -
375 - #[test]
376 - #[should_panic(expected = "[react-compiler] internal error")]
377 - fn dummy_span_panics_loudly() {
378 - let module = swc_ecma_ast::Module {
379 - span: swc_common::DUMMY_SP,
380 - body: vec![swc_ecma_ast::ModuleItem::ModuleDecl(
381 - swc_ecma_ast::ModuleDecl::TsNamespaceExport(swc_ecma_ast::TsNamespaceExportDecl {
382 - span: swc_common::DUMMY_SP,
383 - id: swc_ecma_ast::Ident::new_no_ctxt("Foo".into(), swc_common::DUMMY_SP),
384 - }),
385 - )],
386 - shebang: None,
387 - };
388 - crate::emit_module_to_string(&module, None);
389 - }
390 -}
compiler/crates/react_compiler_swc/tests/integration.rs deleted
-759
@@ -1,759 +0,0 @@
1 -// Copyright (c) Meta Platforms, Inc. and affiliates.
2 -//
3 -// This source code is licensed under the MIT license found in the
4 -// LICENSE file in the root directory of this source tree.
5 -
6 -use react_compiler::entrypoint::plugin_options::CompilerTarget;
7 -use react_compiler::entrypoint::plugin_options::PluginOptions;
8 -use react_compiler_ast::scope::BindingKind;
9 -use react_compiler_ast::scope::ScopeKind;
10 -use react_compiler_ast::statements::Statement;
11 -use react_compiler_swc::convert_ast::convert_module;
12 -use react_compiler_swc::convert_ast_reverse::convert_program_to_swc;
13 -use react_compiler_swc::convert_scope::build_scope_info;
14 -use react_compiler_swc::lint_source;
15 -use react_compiler_swc::prefilter::has_react_like_functions;
16 -use react_compiler_swc::transform_source;
17 -use swc_common::FileName;
18 -use swc_common::SourceMap;
19 -use swc_common::sync::Lrc;
20 -use swc_ecma_ast::EsVersion;
21 -use swc_ecma_parser::EsSyntax;
22 -use swc_ecma_parser::Syntax;
23 -use swc_ecma_parser::parse_file_as_module;
24 -
25 -fn parse_module(source: &str) -> swc_ecma_ast::Module {
26 - let cm = Lrc::new(SourceMap::default());
27 - let fm = cm.new_source_file(Lrc::new(FileName::Anon), source.to_string());
28 - let mut errors = vec![];
29 - parse_file_as_module(
30 - &fm,
31 - Syntax::Es(EsSyntax {
32 - jsx: true,
33 - ..Default::default()
34 - }),
35 - EsVersion::latest(),
36 - None,
37 - &mut errors,
38 - )
39 - .expect("Failed to parse")
40 -}
41 -
42 -fn default_options() -> PluginOptions {
43 - PluginOptions {
44 - should_compile: true,
45 - enable_reanimated: false,
46 - is_dev: false,
47 - filename: None,
48 - compilation_mode: "infer".to_string(),
49 - panic_threshold: "none".to_string(),
50 - target: CompilerTarget::Version("19".to_string()),
51 - gating: None,
52 - dynamic_gating: None,
53 - no_emit: false,
54 - output_mode: None,
55 - eslint_suppression_rules: None,
56 - flow_suppressions: true,
57 - ignore_use_no_forget: false,
58 - custom_opt_out_directives: None,
59 - environment: Default::default(),
60 - source_code: None,
61 - profiling: false,
62 - debug: false,
63 - }
64 -}
65 -
66 -// ── Prefilter tests ─────────────────────────────────────────────────────────
67 -
68 -#[test]
69 -fn prefilter_detects_function_component() {
70 - let module = parse_module("function MyComponent() { return <div />; }");
71 - assert!(has_react_like_functions(&module));
72 -}
73 -
74 -#[test]
75 -fn prefilter_detects_arrow_component() {
76 - let module = parse_module("const MyComponent = () => <div />;");
77 - assert!(has_react_like_functions(&module));
78 -}
79 -
80 -#[test]
81 -fn prefilter_detects_hook() {
82 - let module = parse_module("function useMyHook() { return 42; }");
83 - assert!(has_react_like_functions(&module));
84 -}
85 -
86 -#[test]
87 -fn prefilter_detects_hook_assigned_to_variable() {
88 - let module = parse_module("const useMyHook = function() { return 42; };");
89 - assert!(has_react_like_functions(&module));
90 -}
91 -
92 -#[test]
93 -fn prefilter_rejects_non_react_module() {
94 - let module = parse_module(
95 - r#"
96 - const x = 1;
97 - function helper() { return x + 2; }
98 - export { helper };
99 - "#,
100 - );
101 - assert!(!has_react_like_functions(&module));
102 -}
103 -
104 -#[test]
105 -fn prefilter_rejects_lowercase_function() {
106 - let module = parse_module("function myFunction() { return 42; }");
107 - assert!(!has_react_like_functions(&module));
108 -}
109 -
110 -#[test]
111 -fn prefilter_rejects_use_prefix_without_uppercase() {
112 - let module = parse_module("function useful() { return true; }");
113 - assert!(!has_react_like_functions(&module));
114 -}
115 -
116 -// ── AST round-trip tests ────────────────────────────────────────────────────
117 -
118 -#[test]
119 -fn convert_variable_declaration() {
120 - let source = "const x = 1;";
121 - let module = parse_module(source);
122 - let file = convert_module(&module, source);
123 - assert_eq!(file.program.body.len(), 1);
124 - assert!(matches!(
125 - &file.program.body[0],
126 - Statement::VariableDeclaration(_)
127 - ));
128 -}
129 -
130 -#[test]
131 -fn convert_function_declaration() {
132 - let source = "function foo() { return 42; }";
133 - let module = parse_module(source);
134 - let file = convert_module(&module, source);
135 - assert_eq!(file.program.body.len(), 1);
136 - assert!(matches!(
137 - &file.program.body[0],
138 - Statement::FunctionDeclaration(_)
139 - ));
140 -}
141 -
142 -#[test]
143 -fn convert_arrow_function_expression() {
144 - let source = "const f = (x) => x + 1;";
145 - let module = parse_module(source);
146 - let file = convert_module(&module, source);
147 - assert_eq!(file.program.body.len(), 1);
148 - assert!(matches!(
149 - &file.program.body[0],
150 - Statement::VariableDeclaration(_)
151 - ));
152 -}
153 -
154 -#[test]
155 -fn convert_jsx_element() {
156 - let source = "const el = <div className=\"test\">hello</div>;";
157 - let module = parse_module(source);
158 - let file = convert_module(&module, source);
159 - assert_eq!(file.program.body.len(), 1);
160 - assert!(matches!(
161 - &file.program.body[0],
162 - Statement::VariableDeclaration(_)
163 - ));
164 -}
165 -
166 -#[test]
167 -fn convert_import_declaration() {
168 - let source = "import { useState } from 'react';";
169 - let module = parse_module(source);
170 - let file = convert_module(&module, source);
171 - assert_eq!(file.program.body.len(), 1);
172 - assert!(matches!(
173 - &file.program.body[0],
174 - Statement::ImportDeclaration(_)
175 - ));
176 -}
177 -
178 -#[test]
179 -fn convert_export_named_declaration() {
180 - let source = "export const x = 1;";
181 - let module = parse_module(source);
182 - let file = convert_module(&module, source);
183 - assert_eq!(file.program.body.len(), 1);
184 - assert!(matches!(
185 - &file.program.body[0],
186 - Statement::ExportNamedDeclaration(_)
187 - ));
188 -}
189 -
190 -#[test]
191 -fn convert_export_default_declaration() {
192 - let source = "export default function App() { return <div />; }";
193 - let module = parse_module(source);
194 - let file = convert_module(&module, source);
195 - assert_eq!(file.program.body.len(), 1);
196 - assert!(matches!(
197 - &file.program.body[0],
198 - Statement::ExportDefaultDeclaration(_)
199 - ));
200 -}
201 -
202 -#[test]
203 -fn convert_multiple_statements() {
204 - let source = r#"
205 - import React from 'react';
206 - const x = 1;
207 - function App() { return <div>{x}</div>; }
208 - export default App;
209 - "#;
210 - let module = parse_module(source);
211 - let file = convert_module(&module, source);
212 - assert_eq!(file.program.body.len(), 4);
213 - assert!(matches!(
214 - &file.program.body[0],
215 - Statement::ImportDeclaration(_)
216 - ));
217 - assert!(matches!(
218 - &file.program.body[1],
219 - Statement::VariableDeclaration(_)
220 - ));
221 - assert!(matches!(
222 - &file.program.body[2],
223 - Statement::FunctionDeclaration(_)
224 - ));
225 - assert!(matches!(
226 - &file.program.body[3],
227 - Statement::ExportDefaultDeclaration(_)
228 - ));
229 -}
230 -
231 -#[test]
232 -fn convert_directive() {
233 - let source = "'use strict';\nconst x = 1;";
234 - let module = parse_module(source);
235 - let file = convert_module(&module, source);
236 - assert_eq!(file.program.directives.len(), 1);
237 - assert_eq!(file.program.body.len(), 1);
238 -}
239 -
240 -// ── Scope analysis tests ────────────────────────────────────────────────────
241 -
242 -#[test]
243 -fn scope_program_scope_created() {
244 - let source = "const x = 1;";
245 - let module = parse_module(source);
246 - let info = build_scope_info(&module);
247 - assert!(!info.scopes.is_empty());
248 - assert!(matches!(info.scopes[0].kind, ScopeKind::Program));
249 - assert!(info.scopes[0].parent.is_none());
250 -}
251 -
252 -#[test]
253 -fn scope_var_hoists_to_function() {
254 - let source = r#"
255 - function foo() {
256 - {
257 - var x = 1;
258 - }
259 - }
260 - "#;
261 - let module = parse_module(source);
262 - let info = build_scope_info(&module);
263 -
264 - // Find the binding for x
265 - let x_binding = info
266 - .bindings
267 - .iter()
268 - .find(|b| b.name == "x")
269 - .expect("should find binding x");
270 - assert!(matches!(x_binding.kind, BindingKind::Var));
271 -
272 - // x should be in a Function scope, not the Block scope
273 - let scope = &info.scopes[x_binding.scope.0 as usize];
274 - assert!(matches!(scope.kind, ScopeKind::Function));
275 -}
276 -
277 -#[test]
278 -fn scope_let_const_block_scoped() {
279 - let source = r#"
280 - function foo() {
281 - {
282 - let x = 1;
283 - const y = 2;
284 - }
285 - }
286 - "#;
287 - let module = parse_module(source);
288 - let info = build_scope_info(&module);
289 -
290 - let x_binding = info
291 - .bindings
292 - .iter()
293 - .find(|b| b.name == "x")
294 - .expect("should find binding x");
295 - assert!(matches!(x_binding.kind, BindingKind::Let));
296 - let x_scope = &info.scopes[x_binding.scope.0 as usize];
297 - assert!(matches!(x_scope.kind, ScopeKind::Block));
298 -
299 - let y_binding = info
300 - .bindings
301 - .iter()
302 - .find(|b| b.name == "y")
303 - .expect("should find binding y");
304 - assert!(matches!(y_binding.kind, BindingKind::Const));
305 - let y_scope = &info.scopes[y_binding.scope.0 as usize];
306 - assert!(matches!(y_scope.kind, ScopeKind::Block));
307 -}
308 -
309 -#[test]
310 -fn scope_function_declaration_hoists() {
311 - let source = r#"
312 - function outer() {
313 - {
314 - function inner() {}
315 - }
316 - }
317 - "#;
318 - let module = parse_module(source);
319 - let info = build_scope_info(&module);
320 -
321 - let inner_binding = info
322 - .bindings
323 - .iter()
324 - .find(|b| b.name == "inner")
325 - .expect("should find binding inner");
326 - assert!(matches!(inner_binding.kind, BindingKind::Hoisted));
327 - // inner should be hoisted to the enclosing function scope (outer), not the block
328 - let scope = &info.scopes[inner_binding.scope.0 as usize];
329 - assert!(matches!(scope.kind, ScopeKind::Function));
330 -}
331 -
332 -#[test]
333 -fn scope_import_bindings() {
334 - let source = r#"
335 - import React from 'react';
336 - import { useState, useEffect } from 'react';
337 - import * as Utils from './utils';
338 - "#;
339 - let module = parse_module(source);
340 - let info = build_scope_info(&module);
341 -
342 - let react_binding = info
343 - .bindings
344 - .iter()
345 - .find(|b| b.name == "React")
346 - .expect("should find binding React");
347 - assert!(matches!(react_binding.kind, BindingKind::Module));
348 - assert!(react_binding.import.is_some());
349 - let import_data = react_binding.import.as_ref().unwrap();
350 - assert_eq!(import_data.source, "react");
351 -
352 - let use_state_binding = info
353 - .bindings
354 - .iter()
355 - .find(|b| b.name == "useState")
356 - .expect("should find binding useState");
357 - assert!(matches!(use_state_binding.kind, BindingKind::Module));
358 -
359 - let utils_binding = info
360 - .bindings
361 - .iter()
362 - .find(|b| b.name == "Utils")
363 - .expect("should find binding Utils");
364 - assert!(matches!(utils_binding.kind, BindingKind::Module));
365 -}
366 -
367 -#[test]
368 -fn scope_nested_functions_create_scopes() {
369 - let source = r#"
370 - function outer(a) {
371 - function inner(b) {
372 - return a + b;
373 - }
374 - }
375 - "#;
376 - let module = parse_module(source);
377 - let info = build_scope_info(&module);
378 -
379 - let a_binding = info
380 - .bindings
381 - .iter()
382 - .find(|b| b.name == "a")
383 - .expect("should find binding a");
384 - assert!(matches!(a_binding.kind, BindingKind::Param));
385 -
386 - let b_binding = info
387 - .bindings
388 - .iter()
389 - .find(|b| b.name == "b")
390 - .expect("should find binding b");
391 - assert!(matches!(b_binding.kind, BindingKind::Param));
392 -
393 - // a and b should be in different function scopes
394 - assert!(a_binding.scope.0 != b_binding.scope.0);
395 -}
396 -
397 -#[test]
398 -fn scope_catch_clause_creates_scope() {
399 - let source = r#"
400 - try {
401 - throw new Error();
402 - } catch (e) {
403 - console.log(e);
404 - }
405 - "#;
406 - let module = parse_module(source);
407 - let info = build_scope_info(&module);
408 -
409 - let e_binding = info
410 - .bindings
411 - .iter()
412 - .find(|b| b.name == "e")
413 - .expect("should find binding e");
414 - assert!(matches!(e_binding.kind, BindingKind::Let));
415 - let scope = &info.scopes[e_binding.scope.0 as usize];
416 - assert!(matches!(scope.kind, ScopeKind::Catch));
417 -}
418 -
419 -#[test]
420 -fn scope_arrow_function_params() {
421 - let source = "const f = (x, y) => x + y;";
422 - let module = parse_module(source);
423 - let info = build_scope_info(&module);
424 -
425 - let x_binding = info
426 - .bindings
427 - .iter()
428 - .find(|b| b.name == "x")
429 - .expect("should find binding x");
430 - assert!(matches!(x_binding.kind, BindingKind::Param));
431 - let scope = &info.scopes[x_binding.scope.0 as usize];
432 - assert!(matches!(scope.kind, ScopeKind::Function));
433 -}
434 -
435 -#[test]
436 -fn scope_for_loop_creates_scope() {
437 - let source = "for (let i = 0; i < 10; i++) { console.log(i); }";
438 - let module = parse_module(source);
439 - let info = build_scope_info(&module);
440 -
441 - let i_binding = info
442 - .bindings
443 - .iter()
444 - .find(|b| b.name == "i")
445 - .expect("should find binding i");
446 - assert!(matches!(i_binding.kind, BindingKind::Let));
447 - let scope = &info.scopes[i_binding.scope.0 as usize];
448 - assert!(matches!(scope.kind, ScopeKind::For));
449 -}
450 -
451 -// ── Full transform pipeline tests ───────────────────────────────────────────
452 -
453 -#[test]
454 -fn transform_simple_component_does_not_panic() {
455 - let source = r#"
456 - function App() {
457 - return <div>Hello</div>;
458 - }
459 - "#;
460 - let result = transform_source(source, default_options());
461 - // The transform should complete without panicking.
462 - // It may or may not produce output depending on compiler completeness.
463 - let _ = result.module;
464 - let _ = result.diagnostics;
465 -}
466 -
467 -#[test]
468 -fn transform_component_with_hook_does_not_panic() {
469 - let source = r#"
470 - import { useState } from 'react';
471 - function Counter() {
472 - const [count, setCount] = useState(0);
473 - return <div>{count}</div>;
474 - }
475 - "#;
476 - let result = transform_source(source, default_options());
477 - let _ = result.module;
478 - let _ = result.diagnostics;
479 -}
480 -
481 -#[test]
482 -fn transform_non_react_code_returns_none() {
483 - let source = "const x = 1 + 2;";
484 - let result = transform_source(source, default_options());
485 - // Non-React code with compilation_mode "infer" should be skipped (prefilter)
486 - assert!(result.module.is_none());
487 - assert!(result.diagnostics.is_empty());
488 -}
489 -
490 -#[test]
491 -fn transform_compilation_mode_all_does_not_skip() {
492 - let source = "const x = 1 + 2;";
493 - let mut options = default_options();
494 - options.compilation_mode = "all".to_string();
495 - let result = transform_source(source, options);
496 - // With "all" mode, even non-React code should go through the compiler.
497 - // It may not produce output, but it should not be skipped by prefilter.
498 - let _ = result.module;
499 -}
500 -
501 -#[test]
502 -fn lint_simple_component_does_not_panic() {
503 - let source = r#"
504 - function App() {
505 - return <div>Hello</div>;
506 - }
507 - "#;
508 - let result = lint_source(source, default_options());
509 - let _ = result.diagnostics;
510 -}
511 -
512 -#[test]
513 -fn lint_non_react_code_returns_empty() {
514 - let source = "const x = 1;";
515 - let result = lint_source(source, default_options());
516 - assert!(result.diagnostics.is_empty());
517 -}
518 -
519 -// ── Reverse AST conversion tests ────────────────────────────────────────────
520 -
521 -#[test]
522 -fn reverse_convert_variable_declaration() {
523 - let source = "const x = 1;";
524 - let module = parse_module(source);
525 - let file = convert_module(&module, source);
526 -
527 - let swc_module = convert_program_to_swc(&file);
528 - assert_eq!(swc_module.module.body.len(), 1);
529 - assert!(matches!(
530 - &swc_module.module.body[0],
531 - swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Var(_)))
532 - ));
533 -}
534 -
535 -#[test]
536 -fn reverse_convert_function_declaration() {
537 - let source = "function foo() { return 42; }";
538 - let module = parse_module(source);
539 - let file = convert_module(&module, source);
540 -
541 - let swc_module = convert_program_to_swc(&file);
542 - assert_eq!(swc_module.module.body.len(), 1);
543 - assert!(matches!(
544 - &swc_module.module.body[0],
545 - swc_ecma_ast::ModuleItem::Stmt(swc_ecma_ast::Stmt::Decl(swc_ecma_ast::Decl::Fn(_)))
546 - ));
547 -}
548 -
549 -#[test]
550 -fn reverse_convert_import_export() {
551 - let source = r#"
552 - import { useState } from 'react';
553 - export const x = 1;
554 - "#;
555 - let module = parse_module(source);
556 - let file = convert_module(&module, source);
557 -
558 - let swc_module = convert_program_to_swc(&file);
559 - assert_eq!(swc_module.module.body.len(), 2);
560 -}
561 -
562 -#[test]
563 -fn reverse_convert_roundtrip_via_json() {
564 - let source = r#"
565 - const x = 1;
566 - function foo(a, b) { return a + b; }
567 - "#;
568 - let module = parse_module(source);
569 - let file = convert_module(&module, source);
570 -
571 - // Serialize to JSON and deserialize back
572 - let json = serde_json::to_value(&file).expect("serialize to JSON");
573 - let deserialized: react_compiler_ast::File =
574 - serde_json::from_value(json).expect("deserialize from JSON");
575 -
576 - // Convert the deserialized AST back to SWC
577 - let swc_module = convert_program_to_swc(&deserialized);
578 - assert_eq!(swc_module.module.body.len(), 2);
579 -}
580 -
581 -#[test]
582 -fn reverse_convert_jsx_roundtrip() {
583 - let source = r#"const el = <div className="test">hello</div>;"#;
584 - let module = parse_module(source);
585 - let file = convert_module(&module, source);
586 -
587 - let json = serde_json::to_value(&file).expect("serialize to JSON");
588 - let deserialized: react_compiler_ast::File =
589 - serde_json::from_value(json).expect("deserialize from JSON");
590 -
591 - let swc_module = convert_program_to_swc(&deserialized);
592 - assert_eq!(swc_module.module.body.len(), 1);
593 -}
594 -
595 -#[test]
596 -fn reverse_convert_multiple_statement_types() {
597 - let source = r#"
598 - import React from 'react';
599 - const x = 1;
600 - let y = 'hello';
601 - function App() { return <div>{x}{y}</div>; }
602 - export default App;
603 - "#;
604 - let module = parse_module(source);
605 - let file = convert_module(&module, source);
606 -
607 - let swc_module = convert_program_to_swc(&file);
608 - assert_eq!(swc_module.module.body.len(), 5);
609 -}
610 -
611 -// ── TS module interop statements ────────────────────────────────────────────
612 -
613 -fn parse_ts_module(source: &str) -> swc_ecma_ast::Module {
614 - let cm = Lrc::new(SourceMap::default());
615 - let fm = cm.new_source_file(Lrc::new(FileName::Anon), source.to_string());
616 - let mut errors = vec![];
617 - parse_file_as_module(
618 - &fm,
619 - Syntax::Typescript(swc_ecma_parser::TsSyntax {
620 - tsx: true,
621 - ..Default::default()
622 - }),
623 - EsVersion::latest(),
624 - None,
625 - &mut errors,
626 - )
627 - .expect("Failed to parse")
628 -}
629 -
630 -/// The TS module-interop statements (`import x = require(...)`, `export = x`,
631 -/// `export as namespace X`) flow through `Statement::Unknown` (carrying the
632 -/// raw Babel shape), survive a JSON round trip like the real pipeline, get
633 -/// rebuilt as SWC module declarations, and print back as the source
634 -/// statements. The namespace export exercises the post-emit fixup for the
635 -/// upstream swc_ecma_codegen bug that prints `TsNamespaceExportDecl` as
636 -/// `export = X`.
637 -#[test]
638 -fn ts_module_interop_statements_round_trip() {
639 - let source =
640 - "import lib = require('shared-runtime');\nexport = lib;\nexport as namespace Foo;\n";
641 - let module = parse_ts_module(source);
642 - let file = convert_module(&module, source);
643 -
644 - let expected_types = [
645 - "TSImportEqualsDeclaration",
646 - "TSExportAssignment",
647 - "TSNamespaceExportDeclaration",
648 - ];
649 - assert_eq!(file.program.body.len(), 3);
650 - for (stmt, expected) in file.program.body.iter().zip(expected_types) {
651 - match stmt {
652 - Statement::Unknown(unknown) => assert_eq!(unknown.node_type(), expected),
653 - other => panic!("expected Unknown({expected}), got {other:?}"),
654 - }
655 - }
656 -
657 - // Round trip through JSON like the compiler pipeline does.
658 - let json = serde_json::to_value(&file).expect("serialize to JSON");
659 - let deserialized: react_compiler_ast::File =
660 - serde_json::from_value(json).expect("deserialize from JSON");
661 -
662 - let swc_module = convert_program_to_swc(&deserialized).module;
663 - assert_eq!(swc_module.body.len(), 3);
664 - assert!(matches!(
665 - &swc_module.body[0],
666 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsImportEquals(_))
667 - ));
668 - assert!(matches!(
669 - &swc_module.body[1],
670 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsExportAssignment(_))
671 - ));
672 - assert!(matches!(
673 - &swc_module.body[2],
674 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsNamespaceExport(_))
675 - ));
676 -
677 - let code = react_compiler_swc::emit_with_comments(&swc_module, None, &[]);
678 - assert!(
679 - code.contains("import lib = require(\"shared-runtime\");"),
680 - "missing import-equals in output: {code}"
681 - );
682 - assert!(
683 - code.contains("export = lib"),
684 - "missing export-assignment in output: {code}"
685 - );
686 - assert!(
687 - code.contains("export as namespace Foo;"),
688 - "missing namespace export in output: {code}"
689 - );
690 -}
691 -
692 -/// A qualified-name module reference (`import a = b.c.d`) round-trips
693 -/// through the raw `TSQualifiedName` shape.
694 -#[test]
695 -fn ts_import_equals_qualified_name_round_trips() {
696 - let source = "import a = b.c.d;\n";
697 - let module = parse_ts_module(source);
698 - let file = convert_module(&module, source);
699 -
700 - let json = serde_json::to_value(&file).expect("serialize to JSON");
701 - let deserialized: react_compiler_ast::File =
702 - serde_json::from_value(json).expect("deserialize from JSON");
703 -
704 - let swc_module = convert_program_to_swc(&deserialized).module;
705 - let code = react_compiler_swc::emit_with_comments(&swc_module, None, &[]);
706 - assert!(
707 - code.contains("import a = b.c.d;"),
708 - "missing qualified import-equals in output: {code}"
709 - );
710 -}
711 -
712 -/// Round-trip a TS import-equals declaration source string and return the
713 -/// rebuilt SWC declaration plus the emitted code.
714 -fn round_trip_import_equals(source: &str) -> (swc_ecma_ast::TsImportEqualsDecl, String) {
715 - let module = parse_ts_module(source);
716 - let file = convert_module(&module, source);
717 -
718 - let json = serde_json::to_value(&file).expect("serialize to JSON");
719 - let deserialized: react_compiler_ast::File =
720 - serde_json::from_value(json).expect("deserialize from JSON");
721 -
722 - let swc_module = convert_program_to_swc(&deserialized).module;
723 - let decl = match &swc_module.body[0] {
724 - swc_ecma_ast::ModuleItem::ModuleDecl(swc_ecma_ast::ModuleDecl::TsImportEquals(d)) => {
725 - (**d).clone()
726 - }
727 - other => panic!("expected TsImportEquals, got {other:?}"),
728 - };
729 - let code = react_compiler_swc::emit_with_comments(&swc_module, None, &[]);
730 - (decl, code)
731 -}
732 -
733 -/// `import type x = require(...)` round-trips with `is_type_only`
734 -/// preserved through the raw `importKind: "type"` field.
735 -#[test]
736 -fn ts_import_equals_type_only_round_trips() {
737 - let source = "import type x = require('shared-runtime');\n";
738 - let (decl, code) = round_trip_import_equals(source);
739 - assert!(decl.is_type_only, "is_type_only lost in round trip");
740 - assert!(!decl.is_export);
741 - assert!(
742 - code.contains("import type x = require(\"shared-runtime\");"),
743 - "missing type-only import-equals in output: {code}"
744 - );
745 -}
746 -
747 -/// `export import x = require(...)` round-trips with `is_export` preserved
748 -/// through the raw `isExport: true` field.
749 -#[test]
750 -fn ts_import_equals_export_round_trips() {
751 - let source = "export import x = require('shared-runtime');\n";
752 - let (decl, code) = round_trip_import_equals(source);
753 - assert!(decl.is_export, "is_export lost in round trip");
754 - assert!(!decl.is_type_only);
755 - assert!(
756 - code.contains("export import x = require(\"shared-runtime\");"),
757 - "missing exported import-equals in output: {code}"
758 - );
759 -}
compiler/crates/react_compiler_typeinference/src/infer_types.rs
+200 -131
@@ -13,17 +13,16 @@ use std::collections::HashMap;
13 use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
14 use react_compiler_hir::environment::{Environment, is_hook_name};
15 use react_compiler_hir::object_shape::{
16 - ShapeRegistry,
17 - BUILT_IN_PROPS_ID, BUILT_IN_ARRAY_ID, BUILT_IN_FUNCTION_ID, BUILT_IN_JSX_ID,
18 - BUILT_IN_OBJECT_ID, BUILT_IN_USE_REF_ID, BUILT_IN_REF_VALUE_ID, BUILT_IN_MIXED_READONLY_ID,
19 - BUILT_IN_SET_STATE_ID,
16 + BUILT_IN_ARRAY_ID, BUILT_IN_FUNCTION_ID, BUILT_IN_JSX_ID, BUILT_IN_MIXED_READONLY_ID,
17 + BUILT_IN_OBJECT_ID, BUILT_IN_PROPS_ID, BUILT_IN_REF_VALUE_ID, BUILT_IN_SET_STATE_ID,
18 + BUILT_IN_USE_REF_ID, ShapeRegistry,
19 };
20 use react_compiler_hir::{
21 ArrayPatternElement, BinaryOperator, FunctionId, HirFunction, Identifier, IdentifierId,
23 - IdentifierName, InstructionId, InstructionKind, InstructionValue, JsxAttribute, LoweredFunction,
24 - ManualMemoDependencyRoot, NonLocalBinding, ObjectPropertyKey, ObjectPropertyOrSpread,
25 - ParamPattern, Pattern, PropertyLiteral, PropertyNameKind, ReactFunctionType, SourceLocation,
26 - Terminal, Type, TypeId,
22 + IdentifierName, InstructionId, InstructionKind, InstructionValue, JsxAttribute,
23 + LoweredFunction, ManualMemoDependencyRoot, NonLocalBinding, ObjectPropertyKey,
24 + ObjectPropertyOrSpread, ParamPattern, Pattern, PropertyLiteral, PropertyNameKind,
25 + ReactFunctionType, SourceLocation, Terminal, Type, TypeId,
26 };
27 use react_compiler_ssa::enter_ssa::placeholder_function;
28
@@ -31,7 +30,10 @@ use react_compiler_ssa::enter_ssa::placeholder_function;
30 // Public API
31 // =============================================================================
32
34 -pub fn infer_types(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
33 +pub fn infer_types(
34 + func: &mut HirFunction,
35 + env: &mut Environment,
36 +) -> Result<(), CompilerDiagnostic> {
37 let enable_treat_ref_like_identifiers_as_refs =
38 env.config.enable_treat_ref_like_identifiers_as_refs;
39 let enable_treat_set_identifiers_as_state_setters =
@@ -99,7 +101,8 @@ fn pre_resolve_globals_recursive(
101 // borrow conflicts (we need &env.functions to read, then &mut env for
102 // get_global_declaration).
103 let inner = &env.functions[func_id.0 as usize];
102 - let mut load_globals: Vec<(InstructionId, NonLocalBinding, Option<SourceLocation>)> = Vec::new();
104 + let mut load_globals: Vec<(InstructionId, NonLocalBinding, Option<SourceLocation>)> =
105 + Vec::new();
106 let mut child_func_ids: Vec<FunctionId> = Vec::new();
107
108 for block in inner.body.blocks.values() {
@@ -173,7 +176,10 @@ fn resolve_property_type(
176 _ => {
177 // No shape, but if property name is hook-like, return hook type
178 if let Some(hook_type) = custom_hook_type {
176 - if let PropertyNameKind::Literal { value: PropertyLiteral::String(s) } = property_name {
179 + if let PropertyNameKind::Literal {
180 + value: PropertyLiteral::String(s),
181 + } = property_name
182 + {
183 if is_hook_name(s) {
184 return Some(hook_type.clone());
185 }
@@ -187,7 +193,10 @@ fn resolve_property_type(
193 None => {
194 // Object/Function with no shapeId: TS getPropertyType falls through
195 // to hook-name check, TS getFallthroughPropertyType returns null
190 - if let PropertyNameKind::Literal { value: PropertyLiteral::String(s) } = property_name {
196 + if let PropertyNameKind::Literal {
197 + value: PropertyLiteral::String(s),
198 + } = property_name
199 + {
200 if is_hook_name(s) {
201 return custom_hook_type.cloned();
202 }
@@ -255,10 +264,7 @@ fn type_equals(a: &Type, b: &Type) -> bool {
264 (Type::Primitive, Type::Primitive) => true,
265 (Type::Poly, Type::Poly) => true,
266 (Type::ObjectMethod, Type::ObjectMethod) => true,
258 - (
259 - Type::Object { shape_id: sa },
260 - Type::Object { shape_id: sb },
261 - ) => sa == sb,
267 + (Type::Object { shape_id: sa }, Type::Object { shape_id: sb }) => sa == sb,
268 (
269 Type::Function {
270 return_type: ra, ..
@@ -271,11 +277,7 @@ fn type_equals(a: &Type, b: &Type) -> bool {
277 }
278 }
279
274 -fn set_name(
275 - names: &mut HashMap<IdentifierId, String>,
276 - id: IdentifierId,
277 - source: &Identifier,
278 -) {
280 +fn set_name(names: &mut HashMap<IdentifierId, String>, id: IdentifierId, source: &Identifier) {
281 if let Some(IdentifierName::Named(ref name)) = source.name {
282 names.insert(id, name.clone());
283 }
@@ -295,7 +297,11 @@ fn get_name(names: &HashMap<IdentifierId, String>, id: IdentifierId) -> String {
297 /// `generate_for_function_id` with split borrows instead, because the
298 /// take/replace pattern on `env.functions` requires separate `&mut` access
299 /// to different fields.
298 -fn generate(func: &HirFunction, env: &mut Environment, unifier: &mut Unifier) -> Result<(), CompilerDiagnostic> {
300 +fn generate(
301 + func: &HirFunction,
302 + env: &mut Environment,
303 + unifier: &mut Unifier,
304 +) -> Result<(), CompilerDiagnostic> {
305 // Component params
306 if func.fn_type == ReactFunctionType::Component {
307 if let Some(first) = func.params.first() {
@@ -391,9 +397,19 @@ fn generate(func: &HirFunction, env: &mut Environment, unifier: &mut Unifier) ->
397 // Unify return types
398 let returns_type = get_type(func.returns.identifier, &env.identifiers);
399 if return_types.len() > 1 {
394 - unifier.unify(returns_type, Type::Phi { operands: return_types }, &env.shapes)?;
400 + unifier.unify(
401 + returns_type,
402 + Type::Phi {
403 + operands: return_types,
404 + },
405 + &env.shapes,
406 + )?;
407 } else if return_types.len() == 1 {
396 - unifier.unify(returns_type, return_types.into_iter().next().unwrap(), &env.shapes)?;
408 + unifier.unify(
409 + returns_type,
410 + return_types.into_iter().next().unwrap(),
411 + &env.shapes,
412 + )?;
413 }
414 Ok(())
415 }
@@ -409,10 +425,7 @@ fn generate_for_function_id(
425 unifier: &mut Unifier,
426 ) -> Result<(), CompilerDiagnostic> {
427 // Take the function out temporarily to avoid borrow conflicts
412 - let inner = std::mem::replace(
413 - &mut functions[func_id.0 as usize],
414 - placeholder_function(),
415 - );
428 + let inner = std::mem::replace(&mut functions[func_id.0 as usize], placeholder_function());
429
430 // Process params for component inner functions
431 if inner.fn_type == ReactFunctionType::Component {
@@ -460,7 +473,18 @@ fn generate_for_function_id(
473
474 for &instr_id in &block.instructions {
475 let instr = &inner.instructions[instr_id.0 as usize];
463 - generate_instruction_types(instr, instr_id, func_id.0, identifiers, types, functions, &mut inner_names, global_types, shapes, unifier)?;
476 + generate_instruction_types(
477 + instr,
478 + instr_id,
479 + func_id.0,
480 + identifiers,
481 + types,
482 + functions,
483 + &mut inner_names,
484 + global_types,
485 + shapes,
486 + unifier,
487 + )?;
488 }
489
490 if let Terminal::Return { ref value, .. } = block.terminal {
@@ -516,7 +540,11 @@ fn generate_instruction_types(
540 }
541
542 InstructionValue::LoadLocal { place, .. } => {
519 - set_name(names, instr.lvalue.identifier, &identifiers[place.identifier.0 as usize]);
543 + set_name(
544 + names,
545 + instr.lvalue.identifier,
546 + &identifiers[place.identifier.0 as usize],
547 + );
548 let place_type = get_type(place.identifier, identifiers);
549 unifier.unify(left, place_type, shapes)?;
550 }
@@ -641,7 +669,9 @@ fn generate_instruction_types(
669 )?;
670 }
671
644 - InstructionValue::PropertyLoad { object, property, .. } => {
672 + InstructionValue::PropertyLoad {
673 + object, property, ..
674 + } => {
675 let object_type = get_type(object.identifier, identifiers);
676 let object_name = get_name(names, object.identifier);
677 unifier.unify(
@@ -657,7 +687,9 @@ fn generate_instruction_types(
687 )?;
688 }
689
660 - InstructionValue::ComputedLoad { object, property, .. } => {
690 + InstructionValue::ComputedLoad {
691 + object, property, ..
692 + } => {
693 let object_type = get_type(object.identifier, identifiers);
694 let object_name = get_name(names, object.identifier);
695 let prop_type = get_type(property.identifier, identifiers);
@@ -689,72 +721,70 @@ fn generate_instruction_types(
721 unifier.unify(left, return_type, shapes)?;
722 }
723
692 - InstructionValue::Destructure { lvalue, value, .. } => {
693 - match &lvalue.pattern {
694 - Pattern::Array(array_pattern) => {
695 - for (i, item) in array_pattern.items.iter().enumerate() {
696 - match item {
697 - ArrayPatternElement::Place(place) => {
698 - let item_type = get_type(place.identifier, identifiers);
724 + InstructionValue::Destructure { lvalue, value, .. } => match &lvalue.pattern {
725 + Pattern::Array(array_pattern) => {
726 + for (i, item) in array_pattern.items.iter().enumerate() {
727 + match item {
728 + ArrayPatternElement::Place(place) => {
729 + let item_type = get_type(place.identifier, identifiers);
730 + let value_type = get_type(value.identifier, identifiers);
731 + let object_name = get_name(names, value.identifier);
732 + unifier.unify(
733 + item_type,
734 + Type::Property {
735 + object_type: Box::new(value_type),
736 + object_name,
737 + property_name: PropertyNameKind::Literal {
738 + value: PropertyLiteral::String(i.to_string()),
739 + },
740 + },
741 + shapes,
742 + )?;
743 + }
744 + ArrayPatternElement::Spread(spread) => {
745 + let spread_type = get_type(spread.place.identifier, identifiers);
746 + unifier.unify(
747 + spread_type,
748 + Type::Object {
749 + shape_id: Some(BUILT_IN_ARRAY_ID.to_string()),
750 + },
751 + shapes,
752 + )?;
753 + }
754 + ArrayPatternElement::Hole => {
755 + continue;
756 + }
757 + }
758 + }
759 + }
760 + Pattern::Object(object_pattern) => {
761 + for prop in &object_pattern.properties {
762 + if let ObjectPropertyOrSpread::Property(obj_prop) = prop {
763 + match &obj_prop.key {
764 + ObjectPropertyKey::Identifier { name }
765 + | ObjectPropertyKey::String { name } => {
766 + let prop_place_type =
767 + get_type(obj_prop.place.identifier, identifiers);
768 let value_type = get_type(value.identifier, identifiers);
769 let object_name = get_name(names, value.identifier);
770 unifier.unify(
702 - item_type,
771 + prop_place_type,
772 Type::Property {
773 object_type: Box::new(value_type),
774 object_name,
775 property_name: PropertyNameKind::Literal {
707 - value: PropertyLiteral::String(i.to_string()),
776 + value: PropertyLiteral::String(name.clone()),
777 },
778 },
779 shapes,
780 )?;
781 }
713 - ArrayPatternElement::Spread(spread) => {
714 - let spread_type = get_type(spread.place.identifier, identifiers);
715 - unifier.unify(
716 - spread_type,
717 - Type::Object {
718 - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()),
719 - },
720 - shapes,
721 - )?;
722 - }
723 - ArrayPatternElement::Hole => {
724 - continue;
725 - }
726 - }
727 - }
728 - }
729 - Pattern::Object(object_pattern) => {
730 - for prop in &object_pattern.properties {
731 - if let ObjectPropertyOrSpread::Property(obj_prop) = prop {
732 - match &obj_prop.key {
733 - ObjectPropertyKey::Identifier { name }
734 - | ObjectPropertyKey::String { name } => {
735 - let prop_place_type =
736 - get_type(obj_prop.place.identifier, identifiers);
737 - let value_type = get_type(value.identifier, identifiers);
738 - let object_name = get_name(names, value.identifier);
739 - unifier.unify(
740 - prop_place_type,
741 - Type::Property {
742 - object_type: Box::new(value_type),
743 - object_name,
744 - property_name: PropertyNameKind::Literal {
745 - value: PropertyLiteral::String(name.clone()),
746 - },
747 - },
748 - shapes,
749 - )?;
750 - }
751 - _ => {}
752 - }
782 + _ => {}
783 }
784 }
785 }
786 }
757 - }
787 + },
788
789 InstructionValue::TypeCastExpression { value, .. } => {
790 let value_type = get_type(value.identifier, identifiers);
@@ -770,7 +800,15 @@ fn generate_instruction_types(
800 ..
801 } => {
802 // Recurse into inner function first
773 - generate_for_function_id(*func_id, identifiers, types, functions, global_types, shapes, unifier)?;
803 + generate_for_function_id(
804 + *func_id,
805 + identifiers,
806 + types,
807 + functions,
808 + global_types,
809 + shapes,
810 + unifier,
811 + )?;
812 // Get the inner function's return type
813 let inner_func = &functions[func_id.0 as usize];
814 let inner_return_type = get_type(inner_func.returns.identifier, identifiers);
@@ -793,7 +831,15 @@ fn generate_instruction_types(
831 lowered_func: LoweredFunction { func: func_id },
832 ..
833 } => {
796 - generate_for_function_id(*func_id, identifiers, types, functions, global_types, shapes, unifier)?;
834 + generate_for_function_id(
835 + *func_id,
836 + identifiers,
837 + types,
838 + functions,
839 + global_types,
840 + shapes,
841 + unifier,
842 + )?;
843 unifier.unify(left, Type::ObjectMethod, shapes)?;
844 }
845
@@ -963,56 +1009,56 @@ fn apply_instruction_lvalues(
1009 unifier: &Unifier,
1010 ) {
1011 match value {
966 - InstructionValue::StoreLocal { lvalue, .. } | InstructionValue::StoreContext { lvalue, .. } => {
1012 + InstructionValue::StoreLocal { lvalue, .. }
1013 + | InstructionValue::StoreContext { lvalue, .. } => {
1014 resolve_identifier(lvalue.place.identifier, identifiers, types, unifier);
1015 }
969 - InstructionValue::DeclareLocal { lvalue, .. } | InstructionValue::DeclareContext { lvalue, .. } => {
1016 + InstructionValue::DeclareLocal { lvalue, .. }
1017 + | InstructionValue::DeclareContext { lvalue, .. } => {
1018 resolve_identifier(lvalue.place.identifier, identifiers, types, unifier);
1019 }
972 - InstructionValue::Destructure { lvalue, .. } => {
973 - match &lvalue.pattern {
974 - Pattern::Array(array_pattern) => {
975 - for item in &array_pattern.items {
976 - match item {
977 - ArrayPatternElement::Place(place) => {
978 - resolve_identifier(place.identifier, identifiers, types, unifier);
979 - }
980 - ArrayPatternElement::Spread(spread) => {
981 - resolve_identifier(
982 - spread.place.identifier,
983 - identifiers,
984 - types,
985 - unifier,
986 - );
987 - }
988 - ArrayPatternElement::Hole => {}
1020 + InstructionValue::Destructure { lvalue, .. } => match &lvalue.pattern {
1021 + Pattern::Array(array_pattern) => {
1022 + for item in &array_pattern.items {
1023 + match item {
1024 + ArrayPatternElement::Place(place) => {
1025 + resolve_identifier(place.identifier, identifiers, types, unifier);
1026 + }
1027 + ArrayPatternElement::Spread(spread) => {
1028 + resolve_identifier(
1029 + spread.place.identifier,
1030 + identifiers,
1031 + types,
1032 + unifier,
1033 + );
1034 }
1035 + ArrayPatternElement::Hole => {}
1036 }
1037 }
992 - Pattern::Object(object_pattern) => {
993 - for prop in &object_pattern.properties {
994 - match prop {
995 - ObjectPropertyOrSpread::Property(obj_prop) => {
996 - resolve_identifier(
997 - obj_prop.place.identifier,
998 - identifiers,
999 - types,
1000 - unifier,
1001 - );
1002 - }
1003 - ObjectPropertyOrSpread::Spread(spread) => {
1004 - resolve_identifier(
1005 - spread.place.identifier,
1006 - identifiers,
1007 - types,
1008 - unifier,
1009 - );
1010 - }
1038 + }
1039 + Pattern::Object(object_pattern) => {
1040 + for prop in &object_pattern.properties {
1041 + match prop {
1042 + ObjectPropertyOrSpread::Property(obj_prop) => {
1043 + resolve_identifier(
1044 + obj_prop.place.identifier,
1045 + identifiers,
1046 + types,
1047 + unifier,
1048 + );
1049 + }
1050 + ObjectPropertyOrSpread::Spread(spread) => {
1051 + resolve_identifier(
1052 + spread.place.identifier,
1053 + identifiers,
1054 + types,
1055 + unifier,
1056 + );
1057 }
1058 }
1059 }
1060 }
1015 - }
1061 + },
1062 _ => {}
1063 }
1064 }
@@ -1102,14 +1148,18 @@ fn apply_instruction_operands(
1148 InstructionValue::PropertyLoad { object, .. } => {
1149 resolve_identifier(object.identifier, identifiers, types, unifier);
1150 }
1105 - InstructionValue::PropertyStore { object, value: val, .. } => {
1151 + InstructionValue::PropertyStore {
1152 + object, value: val, ..
1153 + } => {
1154 resolve_identifier(object.identifier, identifiers, types, unifier);
1155 resolve_identifier(val.identifier, identifiers, types, unifier);
1156 }
1157 InstructionValue::PropertyDelete { object, .. } => {
1158 resolve_identifier(object.identifier, identifiers, types, unifier);
1159 }
1112 - InstructionValue::ComputedLoad { object, property, .. } => {
1160 + InstructionValue::ComputedLoad {
1161 + object, property, ..
1162 + } => {
1163 resolve_identifier(object.identifier, identifiers, types, unifier);
1164 resolve_identifier(property.identifier, identifiers, types, unifier);
1165 }
@@ -1123,7 +1173,9 @@ fn apply_instruction_operands(
1173 resolve_identifier(property.identifier, identifiers, types, unifier);
1174 resolve_identifier(val.identifier, identifiers, types, unifier);
1175 }
1126 - InstructionValue::ComputedDelete { object, property, .. } => {
1176 + InstructionValue::ComputedDelete {
1177 + object, property, ..
1178 + } => {
1179 resolve_identifier(object.identifier, identifiers, types, unifier);
1180 resolve_identifier(property.identifier, identifiers, types, unifier);
1181 }
@@ -1156,7 +1208,10 @@ fn apply_instruction_operands(
1208 }
1209 }
1210 InstructionValue::JsxExpression {
1159 - tag, props, children, ..
1211 + tag,
1212 + props,
1213 + children,
1214 + ..
1215 } => {
1216 if let react_compiler_hir::JsxTag::Place(p) = tag {
1217 resolve_identifier(p.identifier, identifiers, types, unifier);
@@ -1190,8 +1245,12 @@ fn apply_instruction_operands(
1245 resolve_identifier(sub.identifier, identifiers, types, unifier);
1246 }
1247 }
1193 - InstructionValue::PrefixUpdate { value: val, lvalue, .. }
1194 - | InstructionValue::PostfixUpdate { value: val, lvalue, .. } => {
1248 + InstructionValue::PrefixUpdate {
1249 + value: val, lvalue, ..
1250 + }
1251 + | InstructionValue::PostfixUpdate {
1252 + value: val, lvalue, ..
1253 + } => {
1254 resolve_identifier(val.identifier, identifiers, types, unifier);
1255 resolve_identifier(lvalue.identifier, identifiers, types, unifier);
1256 }
@@ -1265,7 +1324,12 @@ impl Unifier {
1324 }
1325 }
1326
1268 - fn unify(&mut self, t_a: Type, t_b: Type, shapes: &ShapeRegistry) -> Result<(), CompilerDiagnostic> {
1327 + fn unify(
1328 + &mut self,
1329 + t_a: Type,
1330 + t_b: Type,
1331 + shapes: &ShapeRegistry,
1332 + ) -> Result<(), CompilerDiagnostic> {
1333 self.unify_impl(t_a, t_b, shapes)
1334 }
1335
@@ -1351,7 +1415,12 @@ impl Unifier {
1415 Ok(())
1416 }
1417
1354 - fn bind_variable_to(&mut self, v: Type, ty: Type, shapes: &ShapeRegistry) -> Result<(), CompilerDiagnostic> {
1418 + fn bind_variable_to(
1419 + &mut self,
1420 + v: Type,
1421 + ty: Type,
1422 + shapes: &ShapeRegistry,
1423 + ) -> Result<(), CompilerDiagnostic> {
1424 let v_id = match &v {
1425 Type::TypeVar { id } => *id,
1426 _ => return Ok(()),
compiler/crates/react_compiler_validation/src/lib.rs
+6 -4
@@ -9,22 +9,24 @@ pub mod validate_no_jsx_in_try_statement;
9 pub mod validate_no_ref_access_in_render;
10 pub mod validate_no_set_state_in_effects;
11 pub mod validate_no_set_state_in_render;
12 -pub mod validate_static_components;
12 pub mod validate_preserved_manual_memoization;
13 +pub mod validate_static_components;
14 pub mod validate_use_memo;
15
16 -pub use validate_context_variable_lvalues::{validate_context_variable_lvalues, validate_context_variable_lvalues_with_errors};
16 +pub use validate_context_variable_lvalues::{
17 + validate_context_variable_lvalues, validate_context_variable_lvalues_with_errors,
18 +};
19 pub use validate_exhaustive_dependencies::validate_exhaustive_dependencies;
20 pub use validate_hooks_usage::validate_hooks_usage;
21 pub use validate_locals_not_reassigned_after_render::validate_locals_not_reassigned_after_render;
22 pub use validate_no_capitalized_calls::validate_no_capitalized_calls;
21 -pub use validate_no_derived_computations_in_effects::validate_no_derived_computations_in_effects_exp;
23 pub use validate_no_derived_computations_in_effects::validate_no_derived_computations_in_effects;
24 +pub use validate_no_derived_computations_in_effects::validate_no_derived_computations_in_effects_exp;
25 pub use validate_no_freezing_known_mutable_functions::validate_no_freezing_known_mutable_functions;
26 pub use validate_no_jsx_in_try_statement::validate_no_jsx_in_try_statement;
27 pub use validate_no_ref_access_in_render::validate_no_ref_access_in_render;
28 pub use validate_no_set_state_in_effects::validate_no_set_state_in_effects;
29 pub use validate_no_set_state_in_render::validate_no_set_state_in_render;
28 -pub use validate_static_components::validate_static_components;
30 pub use validate_preserved_manual_memoization::validate_preserved_manual_memoization;
31 +pub use validate_static_components::validate_static_components;
32 pub use validate_use_memo::validate_use_memo;
compiler/crates/react_compiler_validation/src/validate_context_variable_lvalues.rs
+72 -18
@@ -3,12 +3,11 @@ use std::collections::HashMap;
3 use react_compiler_diagnostics::{
4 CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory,
5 };
6 -use react_compiler_hir::{
7 - FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue,
8 - Place,
9 -};
6 use react_compiler_hir::environment::Environment;
7 use react_compiler_hir::visitors::{each_instruction_value_lvalue, each_pattern_operand};
8 +use react_compiler_hir::{
9 + FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, Place,
10 +};
11
12 /// Variable reference kind: local, context, or destructure.
13 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -37,7 +36,12 @@ pub fn validate_context_variable_lvalues(
36 func: &HirFunction,
37 env: &mut Environment,
38 ) -> Result<(), CompilerDiagnostic> {
40 - validate_context_variable_lvalues_with_errors(func, &env.functions, &env.identifiers, &mut env.errors)
39 + validate_context_variable_lvalues_with_errors(
40 + func,
41 + &env.functions,
42 + &env.identifiers,
43 + &mut env.errors,
44 + )
45 }
46
47 /// Like [`validate_context_variable_lvalues`], but writes diagnostics into the
@@ -50,7 +54,13 @@ pub fn validate_context_variable_lvalues_with_errors(
54 errors: &mut CompilerError,
55 ) -> Result<(), CompilerDiagnostic> {
56 let mut identifier_kinds: IdentifierKinds = HashMap::new();
53 - validate_context_variable_lvalues_impl(func, &mut identifier_kinds, functions, identifiers, errors)
57 + validate_context_variable_lvalues_impl(
58 + func,
59 + &mut identifier_kinds,
60 + functions,
61 + identifiers,
62 + errors,
63 + )
64 }
65
66 fn validate_context_variable_lvalues_impl(
@@ -70,25 +80,61 @@ fn validate_context_variable_lvalues_impl(
80 match value {
81 InstructionValue::DeclareContext { lvalue, .. }
82 | InstructionValue::StoreContext { lvalue, .. } => {
73 - visit(identifier_kinds, &lvalue.place, VarRefKind::Context, identifiers, errors)?;
83 + visit(
84 + identifier_kinds,
85 + &lvalue.place,
86 + VarRefKind::Context,
87 + identifiers,
88 + errors,
89 + )?;
90 }
91 InstructionValue::LoadContext { place, .. } => {
76 - visit(identifier_kinds, place, VarRefKind::Context, identifiers, errors)?;
92 + visit(
93 + identifier_kinds,
94 + place,
95 + VarRefKind::Context,
96 + identifiers,
97 + errors,
98 + )?;
99 }
100 InstructionValue::StoreLocal { lvalue, .. }
101 | InstructionValue::DeclareLocal { lvalue, .. } => {
80 - visit(identifier_kinds, &lvalue.place, VarRefKind::Local, identifiers, errors)?;
102 + visit(
103 + identifier_kinds,
104 + &lvalue.place,
105 + VarRefKind::Local,
106 + identifiers,
107 + errors,
108 + )?;
109 }
110 InstructionValue::LoadLocal { place, .. } => {
83 - visit(identifier_kinds, place, VarRefKind::Local, identifiers, errors)?;
111 + visit(
112 + identifier_kinds,
113 + place,
114 + VarRefKind::Local,
115 + identifiers,
116 + errors,
117 + )?;
118 }
119 InstructionValue::PostfixUpdate { lvalue, .. }
120 | InstructionValue::PrefixUpdate { lvalue, .. } => {
87 - visit(identifier_kinds, lvalue, VarRefKind::Local, identifiers, errors)?;
121 + visit(
122 + identifier_kinds,
123 + lvalue,
124 + VarRefKind::Local,
125 + identifiers,
126 + errors,
127 + )?;
128 }
129 InstructionValue::Destructure { lvalue, .. } => {
130 for place in each_pattern_operand(&lvalue.pattern) {
91 - visit(identifier_kinds, &place, VarRefKind::Destructure, identifiers, errors)?;
131 + visit(
132 + identifier_kinds,
133 + &place,
134 + VarRefKind::Destructure,
135 + identifiers,
136 + errors,
137 + )?;
138 }
139 }
140 InstructionValue::FunctionExpression { lowered_func, .. }
@@ -103,11 +149,13 @@ fn validate_context_variable_lvalues_impl(
149 "ValidateContextVariableLValues: unhandled instruction variant",
150 None,
151 )
106 - .with_detail(CompilerDiagnosticDetail::Error {
107 - loc: value.loc().copied(),
108 - message: None,
109 - identifier_name: None,
110 - }),
152 + .with_detail(
153 + CompilerDiagnosticDetail::Error {
154 + loc: value.loc().copied(),
155 + message: None,
156 + identifier_name: None,
157 + },
158 + ),
159 );
160 }
161 }
@@ -118,7 +166,13 @@ fn validate_context_variable_lvalues_impl(
166 // Process inner functions after the block loop to avoid borrow conflicts
167 for func_id in inner_function_ids {
168 let inner_func = &functions[func_id.0 as usize];
121 - validate_context_variable_lvalues_impl(inner_func, identifier_kinds, functions, identifiers, errors)?;
169 + validate_context_variable_lvalues_impl(
170 + inner_func,
171 + identifier_kinds,
172 + functions,
173 + identifiers,
174 + errors,
175 + )?;
176 }
177
178 Ok(())
compiler/crates/react_compiler_validation/src/validate_exhaustive_dependencies.rs
+93 -86
@@ -1,20 +1,20 @@
1 use std::collections::{HashMap, HashSet};
2
3 use react_compiler_diagnostics::{
4 - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerSuggestion,
5 - CompilerSuggestionOperation, ErrorCategory, SourceLocation,
4 + CompilerDiagnostic, CompilerDiagnosticDetail, CompilerSuggestion, CompilerSuggestionOperation,
5 + ErrorCategory, SourceLocation,
6 };
7 use react_compiler_hir::environment::Environment;
8 use react_compiler_hir::environment_config::ExhaustiveEffectDepsMode;
9 +use react_compiler_hir::visitors::{
10 + each_instruction_value_lvalue, each_instruction_value_operand_with_functions,
11 + each_terminal_operand,
12 +};
13 use react_compiler_hir::{
14 ArrayElement, BlockId, DependencyPathEntry, HirFunction, Identifier, IdentifierId,
15 InstructionKind, InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot,
16 NonLocalBinding, ParamPattern, Place, PlaceOrSpread, PropertyLiteral, Terminal, Type,
17 };
14 -use react_compiler_hir::visitors::{
15 - each_instruction_value_lvalue, each_instruction_value_operand_with_functions,
16 - each_terminal_operand,
17 -};
18
19 /// Port of ValidateExhaustiveDependencies.ts
20 ///
@@ -25,7 +25,10 @@ use react_compiler_hir::visitors::{
25 /// Note: takes `&mut HirFunction` (deviating from the read-only validation convention)
26 /// because it sets `has_invalid_deps` on StartMemoize instructions when validation
27 /// errors are found, so that ValidatePreservedManualMemoization can skip those blocks.
28 -pub fn validate_exhaustive_dependencies(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
28 +pub fn validate_exhaustive_dependencies(
29 + func: &mut HirFunction,
30 + env: &mut Environment,
31 +) -> Result<(), CompilerDiagnostic> {
32 let reactive = collect_reactive_identifiers(func, &env.functions);
33 let validate_memo = env.config.validate_exhaustive_memoization_dependencies;
34 let validate_effect = env.config.validate_exhaustive_effect_dependencies.clone();
@@ -74,7 +77,12 @@ pub fn validate_exhaustive_dependencies(func: &mut HirFunction, env: &mut Enviro
77 // Set has_invalid_deps on StartMemoize instructions that had validation errors
78 if !callbacks.invalid_memo_ids.is_empty() {
79 for instr in func.instructions.iter_mut() {
77 - if let InstructionValue::StartMemoize { manual_memo_id, has_invalid_deps, .. } = &mut instr.value {
80 + if let InstructionValue::StartMemoize {
81 + manual_memo_id,
82 + has_invalid_deps,
83 + ..
84 + } = &mut instr.value
85 + {
86 if callbacks.invalid_memo_ids.contains(manual_memo_id) {
87 *has_invalid_deps = true;
88 }
@@ -163,13 +171,7 @@ fn dep_to_key(dep: &InferredDependency) -> InferredDependencyKey {
171
172 fn path_to_string(path: &[DependencyPathEntry]) -> String {
173 path.iter()
166 - .map(|p| {
167 - format!(
168 - "{}{}",
169 - if p.optional { "?." } else { "." },
170 - p.property
171 - )
172 - })
174 + .map(|p| format!("{}{}", if p.optional { "?." } else { "." }, p.property))
175 .collect::<Vec<_>>()
176 .join("")
177 }
@@ -207,9 +209,7 @@ fn is_stable_type(ty: &Type) -> bool {
209 | "BuiltInStartTransition"
210 | "BuiltInSetOptimistic"
211 ),
210 - Type::Object {
211 - shape_id: Some(id),
212 - } => matches!(id.as_str(), "BuiltInUseRefId"),
212 + Type::Object { shape_id: Some(id) } => matches!(id.as_str(), "BuiltInUseRefId"),
213 _ => false,
214 }
215 }
@@ -280,7 +280,10 @@ fn is_sub_path_ignoring_optionals(
280 // Collect reactive identifiers
281 // =============================================================================
282
283 -fn collect_reactive_identifiers(func: &HirFunction, functions: &[HirFunction]) -> HashSet<IdentifierId> {
283 +fn collect_reactive_identifiers(
284 + func: &HirFunction,
285 + functions: &[HirFunction],
286 +) -> HashSet<IdentifierId> {
287 let mut reactive = HashSet::new();
288 for (_block_id, block) in &func.body.blocks {
289 for &instr_id in &block.instructions {
@@ -345,9 +348,9 @@ fn find_optional_places(func: &HirFunction) -> HashMap<IdentifierId, bool> {
348 fallthrough,
349 ..
350 } => {
348 - let is_optional = queue.pop().expect(
349 - "Expected an optional value for each optional test condition",
350 - );
351 + let is_optional = queue
352 + .pop()
353 + .expect("Expected an optional value for each optional test condition");
354 if let Some(opt) = is_optional {
355 optionals.insert(test_place.identifier, opt);
356 }
@@ -355,8 +358,7 @@ fn find_optional_places(func: &HirFunction) -> HashMap<IdentifierId, bool> {
358 // Found the end of the optional chain
359 let consequent_block = &func.body.blocks[consequent];
360 if let Some(last_id) = consequent_block.instructions.last() {
358 - let last_instr =
359 - &func.instructions[last_id.0 as usize];
361 + let last_instr = &func.instructions[last_id.0 as usize];
362 if let InstructionValue::StoreLocal { value, .. } =
363 &last_instr.value
364 {
@@ -378,12 +380,18 @@ fn find_optional_places(func: &HirFunction) -> HashMap<IdentifierId, bool> {
380 queue.push(Some(*opt));
381 test_block_id = *inner_test;
382 }
381 - Terminal::Logical { test: inner_test, .. }
382 - | Terminal::Ternary { test: inner_test, .. } => {
383 + Terminal::Logical {
384 + test: inner_test, ..
385 + }
386 + | Terminal::Ternary {
387 + test: inner_test, ..
388 + } => {
389 queue.push(None);
390 test_block_id = *inner_test;
391 }
386 - Terminal::Sequence { block: seq_block, .. } => {
392 + Terminal::Sequence {
393 + block: seq_block, ..
394 + } => {
395 test_block_id = *seq_block;
396 }
397 Terminal::MaybeThrow { continuation, .. } => {
@@ -631,7 +639,9 @@ fn collect_dependencies(
639 }
640 }
641 }
634 - InstructionValue::DeclareLocal { lvalue: decl_lv, .. } => {
642 + InstructionValue::DeclareLocal {
643 + lvalue: decl_lv, ..
644 + } => {
645 temporaries.insert(
646 decl_lv.place.identifier,
647 Temporary::Local {
@@ -679,7 +689,9 @@ fn collect_dependencies(
689 }
690 }
691 }
682 - InstructionValue::DeclareContext { lvalue: decl_lv, .. } => {
692 + InstructionValue::DeclareContext {
693 + lvalue: decl_lv, ..
694 + } => {
695 temporaries.insert(
696 decl_lv.place.identifier,
697 Temporary::Local {
@@ -747,11 +759,9 @@ fn collect_dependencies(
759 } => {
760 // Number properties or ref.current: visit the object directly
761 let is_numeric = matches!(property, PropertyLiteral::Number(_));
750 - let is_ref_current = is_use_ref_type(get_identifier_type(
751 - object.identifier,
752 - identifiers,
753 - types,
754 - )) && *property == PropertyLiteral::String("current".to_string());
762 + let is_ref_current =
763 + is_use_ref_type(get_identifier_type(object.identifier, identifiers, types))
764 + && *property == PropertyLiteral::String("current".to_string());
765
766 if is_numeric || is_ref_current {
767 visit_candidate_dependency(
@@ -791,12 +801,8 @@ fn collect_dependencies(
801 }
802 }
803 }
794 - InstructionValue::FunctionExpression {
795 - lowered_func, ..
796 - }
797 - | InstructionValue::ObjectMethod {
798 - lowered_func, ..
799 - } => {
804 + InstructionValue::FunctionExpression { lowered_func, .. }
805 + | InstructionValue::ObjectMethod { lowered_func, .. } => {
806 let inner_func = &functions[lowered_func.func.0 as usize];
807 let function_deps = collect_dependencies(
808 inner_func,
@@ -938,8 +944,7 @@ fn collect_dependencies(
944 InstructionValue::CallExpression { callee, args, .. } => {
945 // Check if this is an effect hook call
946 if let Some(cb) = callbacks.as_mut() {
941 - let callee_ty =
942 - get_identifier_type(callee.identifier, identifiers, types);
947 + let callee_ty = get_identifier_type(callee.identifier, identifiers, types);
948 if is_effect_hook(callee_ty)
949 && !matches!(cb.validate_effect, ExhaustiveEffectDepsMode::Off)
950 {
@@ -1034,7 +1039,9 @@ fn collect_dependencies(
1039 }
1040
1041 // Visit all operands except for MethodCall's property
1037 - for operand in each_instruction_value_operand_with_functions(&instr.value, functions) {
1042 + for operand in
1043 + each_instruction_value_operand_with_functions(&instr.value, functions)
1044 + {
1045 visit_candidate_dependency(
1046 &operand,
1047 temporaries,
@@ -1052,8 +1059,7 @@ fn collect_dependencies(
1059 } => {
1060 // Check if this is an effect hook call
1061 if let Some(cb) = callbacks.as_mut() {
1055 - let prop_ty =
1056 - get_identifier_type(property.identifier, identifiers, types);
1062 + let prop_ty = get_identifier_type(property.identifier, identifiers, types);
1063 if is_effect_hook(prop_ty)
1064 && !matches!(cb.validate_effect, ExhaustiveEffectDepsMode::Off)
1065 {
@@ -1171,7 +1177,9 @@ fn collect_dependencies(
1177 }
1178 _ => {
1179 // Default: visit all operands
1174 - for operand in each_instruction_value_operand_with_functions(&instr.value, functions) {
1180 + for operand in
1181 + each_instruction_value_operand_with_functions(&instr.value, functions)
1182 + {
1183 visit_candidate_dependency(
1184 &operand,
1185 temporaries,
@@ -1226,9 +1234,10 @@ fn validate_dependencies(
1234 // Sort dependencies by name and path
1235 inferred.sort_by(|a, b| {
1236 match (a, b) {
1229 - (InferredDependency::Global { binding: ab }, InferredDependency::Global { binding: bb }) => {
1230 - ab.name().cmp(bb.name())
1231 - }
1237 + (
1238 + InferredDependency::Global { binding: ab },
1239 + InferredDependency::Global { binding: bb },
1240 + ) => ab.name().cmp(bb.name()),
1241 (
1242 InferredDependency::Local {
1243 identifier: a_id,
@@ -1257,7 +1266,8 @@ fn validate_dependencies(
1266 if a_opt != b_opt {
1267 return a_opt.cmp(&b_opt);
1268 }
1260 - let prop_cmp = ap.property.to_string().cmp(&bp.property.to_string());
1269 + let prop_cmp =
1270 + ap.property.to_string().cmp(&bp.property.to_string());
1271 if prop_cmp != std::cmp::Ordering::Equal {
1272 return prop_cmp;
1273 }
@@ -1268,7 +1278,12 @@ fn validate_dependencies(
1278 _ => std::cmp::Ordering::Equal,
1279 }
1280 }
1271 - (InferredDependency::Global { binding: ab }, InferredDependency::Local { identifier: b_id, .. }) => {
1281 + (
1282 + InferredDependency::Global { binding: ab },
1283 + InferredDependency::Local {
1284 + identifier: b_id, ..
1285 + },
1286 + ) => {
1287 let a_name = ab.name();
1288 let b_name = get_identifier_name(*b_id, identifiers);
1289 match b_name.as_deref() {
@@ -1276,7 +1291,12 @@ fn validate_dependencies(
1291 None => std::cmp::Ordering::Equal,
1292 }
1293 }
1279 - (InferredDependency::Local { identifier: a_id, .. }, InferredDependency::Global { binding: bb }) => {
1294 + (
1295 + InferredDependency::Local {
1296 + identifier: a_id, ..
1297 + },
1298 + InferredDependency::Global { binding: bb },
1299 + ) => {
1300 let a_name = get_identifier_name(*a_id, identifiers);
1301 let b_name = bb.name();
1302 match a_name.as_deref() {
@@ -1382,8 +1402,7 @@ fn validate_dependencies(
1402 }
1403 }
1404
1385 - if has_matching
1386 - || is_optional_dependency(*identifier, reactive, identifiers, types)
1405 + if has_matching || is_optional_dependency(*identifier, reactive, identifiers, types)
1406 {
1407 continue;
1408 }
@@ -1398,7 +1417,10 @@ fn validate_dependencies(
1417 if matched.contains(&i) {
1418 continue;
1419 }
1401 - if let ManualMemoDependencyRoot::NamedLocal { constant, value, .. } = &dep.root {
1420 + if let ManualMemoDependencyRoot::NamedLocal {
1421 + constant, value, ..
1422 + } = &dep.root
1423 + {
1424 if *constant {
1425 let dep_ty = get_identifier_type(value.identifier, identifiers, types);
1426 // Constant-folded primitives: skip
@@ -1469,7 +1491,10 @@ fn validate_dependencies(
1491 // Add detail items for missing deps
1492 for dep in &filtered_missing {
1493 if let InferredDependency::Local {
1472 - identifier, path: _, loc, ..
1494 + identifier,
1495 + path: _,
1496 + loc,
1497 + ..
1498 } = dep
1499 {
1500 let mut hint = String::new();
@@ -1517,8 +1542,7 @@ fn validate_dependencies(
1542
1543 if let Some(matching) = matching_inferred {
1544 if let InferredDependency::Local { identifier, .. } = matching {
1520 - let matching_ty =
1521 - get_identifier_type(*identifier, identifiers, types);
1545 + let matching_ty = get_identifier_type(*identifier, identifiers, types);
1546 if is_effect_event_function_type(matching_ty) {
1547 let dep_str = print_manual_memo_dependency(dep, identifiers);
1548 diagnostic.details.push(CompilerDiagnosticDetail::Error {
@@ -1535,8 +1559,7 @@ fn validate_dependencies(
1559 types,
1560 ) {
1561 let dep_str = print_manual_memo_dependency(dep, identifiers);
1538 - let inferred_str =
1539 - print_inferred_dependency(matching, identifiers);
1562 + let inferred_str = print_inferred_dependency(matching, identifiers);
1563 diagnostic.details.push(CompilerDiagnosticDetail::Error {
1564 loc: dep.loc.or(manual_memo_loc),
1565 message: Some(format!(
@@ -1594,13 +1617,7 @@ fn print_inferred_dependency(dep: &InferredDependency, identifiers: &[Identifier
1617 .unwrap_or_else(|| "<unnamed>".to_string());
1618 let path_str: String = path
1619 .iter()
1597 - .map(|p| {
1598 - format!(
1599 - "{}.{}",
1600 - if p.optional { "?" } else { "" },
1601 - p.property
1602 - )
1603 - })
1620 + .map(|p| format!("{}.{}", if p.optional { "?" } else { "" }, p.property))
1621 .collect();
1622 format!("{name}{path_str}")
1623 }
@@ -1618,13 +1635,7 @@ fn print_manual_memo_dependency(dep: &ManualMemoDependency, identifiers: &[Ident
1635 let path_str: String = dep
1636 .path
1637 .iter()
1621 - .map(|p| {
1622 - format!(
1623 - "{}.{}",
1624 - if p.optional { "?" } else { "" },
1625 - p.property
1626 - )
1627 - })
1638 + .map(|p| format!("{}.{}", if p.optional { "?" } else { "" }, p.property))
1639 .collect();
1640 format!("{name}{path_str}")
1641 }
@@ -1666,9 +1677,10 @@ fn is_optional_dependency_inferred(
1677
1678 fn is_equal_temporary(a: &InferredDependency, b: &InferredDependency) -> bool {
1679 match (a, b) {
1669 - (InferredDependency::Global { binding: ab }, InferredDependency::Global { binding: bb }) => {
1670 - ab.name() == bb.name()
1671 - }
1680 + (
1681 + InferredDependency::Global { binding: ab },
1682 + InferredDependency::Global { binding: bb },
1683 + ) => ab.name() == bb.name(),
1684 (
1685 InferredDependency::Local {
1686 identifier: a_id,
@@ -1709,10 +1721,8 @@ fn create_diagnostic(
1721
1722 let (reason, description) = match category {
1723 ErrorCategory::MemoDependencies => {
1712 - let reason_parts: Vec<&str> = [missing_str, extra_str]
1713 - .iter()
1714 - .filter_map(|x| *x)
1715 - .collect();
1724 + let reason_parts: Vec<&str> =
1725 + [missing_str, extra_str].iter().filter_map(|x| *x).collect();
1726 let reason = format!("Found {} memoization dependencies", reason_parts.join("/"));
1727
1728 let desc_parts: Vec<&str> = [
@@ -1734,10 +1744,8 @@ fn create_diagnostic(
1744 (reason, description)
1745 }
1746 ErrorCategory::EffectExhaustiveDependencies => {
1737 - let reason_parts: Vec<&str> = [missing_str, extra_str]
1738 - .iter()
1739 - .filter_map(|x| *x)
1740 - .collect();
1747 + let reason_parts: Vec<&str> =
1748 + [missing_str, extra_str].iter().filter_map(|x| *x).collect();
1749 let reason = format!("Found {} effect dependencies", reason_parts.join("/"));
1750
1751 let desc_parts: Vec<&str> = [
@@ -1788,4 +1796,3 @@ fn each_instruction_lvalue_ids(
1796 }
1797 ids
1798 }
1791 -
compiler/crates/react_compiler_validation/src/validate_hooks_usage.rs
+24 -35
@@ -16,15 +16,14 @@ use indexmap::IndexMap;
16 use react_compiler_diagnostics::{
17 CompilerDiagnostic, CompilerError, CompilerErrorDetail, ErrorCategory, SourceLocation,
18 };
19 -use react_compiler_hir::{
20 - FunctionId, HirFunction, Identifier, IdentifierId,
21 - InstructionValue, ParamPattern, Place, PropertyLiteral,
22 - Type, visitors,
23 -};
24 -use react_compiler_hir::visitors::{each_pattern_operand, each_terminal_operand};
19 use react_compiler_hir::dominator::compute_unconditional_blocks;
26 -use react_compiler_hir::environment::{is_hook_name, Environment};
20 +use react_compiler_hir::environment::{Environment, is_hook_name};
21 use react_compiler_hir::object_shape::HookKind;
22 +use react_compiler_hir::visitors::{each_pattern_operand, each_terminal_operand};
23 +use react_compiler_hir::{
24 + FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, ParamPattern, Place,
25 + PropertyLiteral, Type, visitors,
26 +};
27
28 /// Value classification for hook validation.
29 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -195,7 +194,10 @@ fn record_dynamic_hook_usage_error(
194 }
195
196 /// Validates hooks usage rules for a function.
198 -pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result<(), react_compiler_diagnostics::CompilerDiagnostic> {
197 +pub fn validate_hooks_usage(
198 + func: &HirFunction,
199 + env: &mut Environment,
200 +) -> Result<(), react_compiler_diagnostics::CompilerDiagnostic> {
201 let unconditional_blocks = compute_unconditional_blocks(func, env.next_block_id().0)?;
202 let mut errors_by_loc: IndexMap<SourceLocation, CompilerErrorDetail> = IndexMap::new();
203 let mut value_kinds: HashMap<IdentifierId, Kind> = HashMap::new();
@@ -267,8 +269,7 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
269 InstructionValue::PropertyLoad {
270 object, property, ..
271 } => {
270 - let object_kind =
271 - get_kind_for_place(object, &value_kinds, &env.identifiers);
272 + let object_kind = get_kind_for_place(object, &value_kinds, &env.identifiers);
273 let is_hook_property = match property {
274 PropertyLiteral::String(s) => is_hook_name(s),
275 PropertyLiteral::Number(_) => false,
@@ -301,8 +302,7 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
302 value_kinds.insert(lvalue_id, kind);
303 }
304 InstructionValue::CallExpression { callee, args, .. } => {
304 - let callee_kind =
305 - get_kind_for_place(callee, &value_kinds, &env.identifiers);
305 + let callee_kind = get_kind_for_place(callee, &value_kinds, &env.identifiers);
306 let is_hook_callee =
307 callee_kind == Kind::KnownHook || callee_kind == Kind::PotentialHook;
308 if is_hook_callee && !unconditional_blocks.contains(&block.id) {
@@ -330,8 +330,7 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
330 args,
331 ..
332 } => {
333 - let callee_kind =
334 - get_kind_for_place(property, &value_kinds, &env.identifiers);
333 + let callee_kind = get_kind_for_place(property, &value_kinds, &env.identifiers);
334 let is_hook_callee =
335 callee_kind == Kind::KnownHook || callee_kind == Kind::PotentialHook;
336 if is_hook_callee && !unconditional_blocks.contains(&block.id) {
@@ -342,11 +341,7 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
341 env,
342 )?;
343 } else if callee_kind == Kind::PotentialHook {
345 - record_dynamic_hook_usage_error(
346 - property,
347 - &mut errors_by_loc,
348 - env,
349 - )?;
344 + record_dynamic_hook_usage_error(property, &mut errors_by_loc, env)?;
345 }
346 // Visit receiver and args (not property)
347 visit_place(receiver, &value_kinds, &mut errors_by_loc, env)?;
@@ -360,12 +355,11 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
355 }
356 InstructionValue::Destructure { lvalue, value, .. } => {
357 visit_place(value, &value_kinds, &mut errors_by_loc, env)?;
363 - let object_kind =
364 - get_kind_for_place(value, &value_kinds, &env.identifiers);
358 + let object_kind = get_kind_for_place(value, &value_kinds, &env.identifiers);
359 // Process instr.lvalue and all pattern operands (matching TS eachInstructionLValue)
360 let pattern_places = each_pattern_operand(&lvalue.pattern);
367 - let all_lvalues = std::iter::once(instr.lvalue.clone())
368 - .chain(pattern_places.into_iter());
361 + let all_lvalues =
362 + std::iter::once(instr.lvalue.clone()).chain(pattern_places.into_iter());
363 for place in all_lvalues {
364 let is_hook_property =
365 ident_is_hook_name(place.identifier, &env.identifiers);
@@ -398,20 +392,13 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
392 _ => {
393 // For all other instructions: visit operands, set lvalue kinds
394 // Matches TS which uses eachInstructionOperand + eachInstructionLValue
401 - visit_all_operands(
402 - &instr.value,
403 - &value_kinds,
404 - &mut errors_by_loc,
405 - env,
406 - )?;
395 + visit_all_operands(&instr.value, &value_kinds, &mut errors_by_loc, env)?;
396 // Set kind for instr.lvalue
408 - let kind =
409 - get_kind_for_place(&instr.lvalue, &value_kinds, &env.identifiers);
397 + let kind = get_kind_for_place(&instr.lvalue, &value_kinds, &env.identifiers);
398 value_kinds.insert(lvalue_id, kind);
399 // Also set kind for value-level lvalues (e.g. DeclareLocal, PrefixUpdate, PostfixUpdate)
400 for lv in visitors::each_instruction_value_lvalue(&instr.value) {
413 - let lv_kind =
414 - get_kind_for_place(&lv, &value_kinds, &env.identifiers);
401 + let lv_kind = get_kind_for_place(&lv, &value_kinds, &env.identifiers);
402 value_kinds.insert(lv.identifier, lv_kind);
403 }
404 }
@@ -434,7 +421,10 @@ pub fn validate_hooks_usage(func: &HirFunction, env: &mut Environment) -> Result
421 /// Visit a function expression to check for hook calls inside it.
422 /// Processes instructions in order, visiting nested functions immediately
423 /// (before processing subsequent calls) to match TS error ordering.
437 -fn visit_function_expression(env: &mut Environment, func_id: FunctionId) -> Result<(), CompilerError> {
424 +fn visit_function_expression(
425 + env: &mut Environment,
426 + func_id: FunctionId,
427 +) -> Result<(), CompilerError> {
428 // Collect items in instruction order to process them sequentially.
429 // Each item is either a call to check or a nested function to visit.
430 enum Item {
@@ -532,4 +522,3 @@ fn visit_all_operands(
522 }
523 Ok(())
524 }
535 -
compiler/crates/react_compiler_validation/src/validate_locals_not_reassigned_after_render.rs
+17 -29
@@ -7,15 +7,14 @@
7
8 use std::collections::{HashMap, HashSet};
9
10 -use react_compiler_diagnostics::{
11 - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory,
12 -};
10 +use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory};
11 use react_compiler_hir::environment::Environment;
12 +use react_compiler_hir::visitors::{
13 + each_instruction_lvalue_ids, each_instruction_value_operand, each_terminal_operand,
14 +};
15 use react_compiler_hir::{
15 - Effect, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue,
16 - Place, Type,
16 + Effect, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue, Place, Type,
17 };
18 -use react_compiler_hir::visitors::{each_instruction_lvalue_ids, each_instruction_value_operand, each_terminal_operand};
18
19 /// Validates that local variables cannot be reassigned after render.
20 /// This prevents a category of bugs in which a closure captures a
@@ -76,7 +75,6 @@ fn format_variable_name(place: &Place, identifiers: &[Identifier]) -> String {
75 }
76 }
77
79 -
78 /// Recursively checks whether a function (or its dependencies) reassigns a
79 /// context variable. Returns the reassigned place if found, or None.
80 ///
@@ -148,14 +146,13 @@ fn get_context_reassignment(
146 .to_string(),
147 ),
148 )
151 - .with_detail(CompilerDiagnosticDetail::Error {
152 - loc: reassignment_place.loc,
153 - message: Some(format!(
154 - "Cannot reassign {}",
155 - variable_name
156 - )),
157 - identifier_name: None,
158 - }),
149 + .with_detail(
150 + CompilerDiagnosticDetail::Error {
151 + loc: reassignment_place.loc,
152 + message: Some(format!("Cannot reassign {}", variable_name)),
153 + identifier_name: None,
154 + },
155 + ),
156 );
157 // Return null (don't propagate further) — matches TS behavior
158 return None;
@@ -168,21 +165,16 @@ fn get_context_reassignment(
165 }
166
167 InstructionValue::StoreLocal { lvalue, value, .. } => {
171 - if let Some(reassignment_place) =
172 - reassigning_functions.get(&value.identifier)
173 - {
168 + if let Some(reassignment_place) = reassigning_functions.get(&value.identifier) {
169 let reassignment_place = reassignment_place.clone();
170 reassigning_functions
171 .insert(lvalue.place.identifier, reassignment_place.clone());
177 - reassigning_functions
178 - .insert(instr.lvalue.identifier, reassignment_place);
172 + reassigning_functions.insert(instr.lvalue.identifier, reassignment_place);
173 }
174 }
175
176 InstructionValue::LoadLocal { place, .. } => {
183 - if let Some(reassignment_place) =
184 - reassigning_functions.get(&place.identifier)
185 - {
177 + if let Some(reassignment_place) = reassigning_functions.get(&place.identifier) {
178 reassigning_functions
179 .insert(instr.lvalue.identifier, reassignment_place.clone());
180 }
@@ -209,14 +201,11 @@ fn get_context_reassignment(
201 }
202
203 // Propagate reassigning function info through StoreContext
212 - if let Some(reassignment_place) =
213 - reassigning_functions.get(&value.identifier)
214 - {
204 + if let Some(reassignment_place) = reassigning_functions.get(&value.identifier) {
205 let reassignment_place = reassignment_place.clone();
206 reassigning_functions
207 .insert(lvalue.place.identifier, reassignment_place.clone());
218 - reassigning_functions
219 - .insert(instr.lvalue.identifier, reassignment_place);
208 + reassigning_functions.insert(instr.lvalue.identifier, reassignment_place);
209 }
210 }
211
@@ -290,4 +279,3 @@ fn get_context_reassignment(
279
280 None
281 }
293 -
compiler/crates/react_compiler_validation/src/validate_no_capitalized_calls.rs
+6 -6
@@ -7,7 +7,10 @@ use react_compiler_hir::{HirFunction, IdentifierId, InstructionValue, PropertyLi
7 /// Validates that capitalized functions are not called directly (they should be rendered as JSX).
8 ///
9 /// Port of ValidateNoCapitalizedCalls.ts.
10 -pub fn validate_no_capitalized_calls(func: &HirFunction, env: &mut Environment) -> Result<(), CompilerError> {
10 +pub fn validate_no_capitalized_calls(
11 + func: &HirFunction,
12 + env: &mut Environment,
13 +) -> Result<(), CompilerError> {
14 // Build the allow list from global registry keys + config entries
15 let mut allow_list: HashSet<String> = env.globals().keys().cloned().collect();
16 if let Some(config_entries) = &env.config.validate_no_capitalized_calls {
@@ -55,14 +58,11 @@ pub fn validate_no_capitalized_calls(func: &HirFunction, env: &mut Environment)
58 InstructionValue::PropertyLoad { property, .. } => {
59 if let PropertyLiteral::String(prop_name) = property {
60 if prop_name.starts_with(|c: char| c.is_ascii_uppercase()) {
58 - capitalized_properties
59 - .insert(lvalue_id, prop_name.clone());
61 + capitalized_properties.insert(lvalue_id, prop_name.clone());
62 }
63 }
64 }
63 - InstructionValue::MethodCall {
64 - property, loc, ..
65 - } => {
65 + InstructionValue::MethodCall { property, loc, .. } => {
66 let property_id = property.identifier;
67 if let Some(prop_name) = capitalized_properties.get(&property_id) {
68 env.record_error(CompilerErrorDetail {
compiler/crates/react_compiler_validation/src/validate_no_derived_computations_in_effects.rs
+88 -53
@@ -16,15 +16,14 @@ use react_compiler_diagnostics::{
16 CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, CompilerErrorDetail, ErrorCategory,
17 };
18 use react_compiler_hir::environment::Environment;
19 +use react_compiler_hir::visitors::{
20 + each_instruction_lvalue_ids, each_instruction_operand as canonical_each_instruction_operand,
21 +};
22 use react_compiler_hir::{
20 - is_set_state_type, is_use_effect_hook_type, is_use_ref_type, is_use_state_type,
23 ArrayElement, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, Identifier,
22 - IdentifierId, IdentifierName, InstructionValue, ParamPattern, PlaceOrSpread,
23 - ReactFunctionType, ReturnVariant, SourceLocation, Type,
24 -};
25 -use react_compiler_hir::visitors::{
26 - each_instruction_lvalue_ids,
27 - each_instruction_operand as canonical_each_instruction_operand,
24 + IdentifierId, IdentifierName, InstructionValue, ParamPattern, PlaceOrSpread, ReactFunctionType,
25 + ReturnVariant, SourceLocation, Type, is_set_state_type, is_use_effect_hook_type,
26 + is_use_ref_type, is_use_state_type,
27 };
28
29 /// Get the user-visible name for an identifier, matching Babel's
@@ -84,7 +83,11 @@ fn get_identifier_name_with_loc(
83 }
84 if let (Some(start), Some(end)) = (byte_start, byte_end) {
85 let slice = &code[start..end];
87 - if !slice.is_empty() && slice.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '$') {
86 + if !slice.is_empty()
87 + && slice
88 + .chars()
89 + .all(|c| c.is_alphanumeric() || c == '_' || c == '$')
90 + {
91 return Some(slice.to_string());
92 }
93 }
@@ -348,19 +351,23 @@ fn maybe_record_set_state_for_instr(
351
352 let root = get_root_set_state(lvalue_id, set_state_loads, &mut HashSet::new());
353 if let Some(root_id) = root {
351 - set_state_usages
352 - .entry(root_id)
353 - .or_insert_with(|| {
354 - let mut set = HashSet::new();
355 - set.insert(LocKey::from_loc(&instr.lvalue.loc));
356 - set
357 - });
354 + set_state_usages.entry(root_id).or_insert_with(|| {
355 + let mut set = HashSet::new();
356 + set.insert(LocKey::from_loc(&instr.lvalue.loc));
357 + set
358 + });
359 }
360 }
361 }
362
362 -fn is_mutable_at(env: &Environment, eval_order: EvaluationOrder, identifier_id: IdentifierId) -> bool {
363 - env.identifiers[identifier_id.0 as usize].mutable_range.contains(eval_order)
363 +fn is_mutable_at(
364 + env: &Environment,
365 + eval_order: EvaluationOrder,
366 + identifier_id: IdentifierId,
367 +) -> bool {
368 + env.identifiers[identifier_id.0 as usize]
369 + .mutable_range
370 + .contains(eval_order)
371 }
372
373 pub fn validate_no_derived_computations_in_effects_exp(
@@ -473,7 +480,8 @@ fn record_phi_derivations(
480 let mut source_ids: indexmap::IndexSet<IdentifierId> = indexmap::IndexSet::new();
481
482 for (_block_id, operand) in &phi.operands {
476 - if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand.identifier) {
483 + if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand.identifier)
484 + {
485 type_of_value = join_value(type_of_value, operand_metadata.type_of_value);
486 source_ids.insert(operand.identifier);
487 }
@@ -525,22 +533,29 @@ fn record_instruction_derivations(
533 record_phi_derivations(block, context, env);
534 for &inner_instr_id in &block.instructions {
535 let inner_instr = &inner_func.instructions[inner_instr_id.0 as usize];
528 - record_instruction_derivations(inner_instr, context, is_first_pass, inner_func, env)?;
536 + record_instruction_derivations(
537 + inner_instr,
538 + context,
539 + is_first_pass,
540 + inner_func,
541 + env,
542 + )?;
543 }
544 }
545 }
546 InstructionValue::CallExpression { callee, args, .. } => {
547 let callee_type = &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];
534 - if is_use_effect_hook_type(callee_type)
535 - && args.len() == 2
536 - {
548 + if is_use_effect_hook_type(callee_type) && args.len() == 2 {
549 if let (
550 react_compiler_hir::PlaceOrSpread::Place(arg0),
551 react_compiler_hir::PlaceOrSpread::Place(arg1),
552 ) = (&args[0], &args[1])
553 {
554 let effect_function = context.functions.get(&arg0.identifier).copied();
543 - let deps = context.candidate_dependencies.get(&arg1.identifier).cloned();
555 + let deps = context
556 + .candidate_dependencies
557 + .get(&arg1.identifier)
558 + .cloned();
559 if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) {
560 context.effects_cache.insert(
561 arg0.identifier,
@@ -569,16 +584,17 @@ fn record_instruction_derivations(
584 }
585 InstructionValue::MethodCall { property, args, .. } => {
586 let prop_type = &types[identifiers[property.identifier.0 as usize].type_.0 as usize];
572 - if is_use_effect_hook_type(prop_type)
573 - && args.len() == 2
574 - {
587 + if is_use_effect_hook_type(prop_type) && args.len() == 2 {
588 if let (
589 react_compiler_hir::PlaceOrSpread::Place(arg0),
590 react_compiler_hir::PlaceOrSpread::Place(arg1),
591 ) = (&args[0], &args[1])
592 {
593 let effect_function = context.functions.get(&arg0.identifier).copied();
581 - let deps = context.candidate_dependencies.get(&arg1.identifier).cloned();
594 + let deps = context
595 + .candidate_dependencies
596 + .get(&arg1.identifier)
597 + .cloned();
598 if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) {
599 context.effects_cache.insert(
600 arg0.identifier,
@@ -616,7 +632,9 @@ fn record_instruction_derivations(
632 _ => None,
633 })
634 .collect();
619 - context.candidate_dependencies.insert(lvalue_id, dep_elements);
635 + context
636 + .candidate_dependencies
637 + .insert(lvalue_id, dep_elements);
638 }
639 _ => {}
640 }
@@ -625,7 +643,8 @@ fn record_instruction_derivations(
643 for (operand_id, operand_loc) in each_instruction_operand(instr, env) {
644 // Track setState usages
645 if context.set_state_loads.contains_key(&operand_id) {
628 - let root = get_root_set_state(operand_id, &context.set_state_loads, &mut HashSet::new());
646 + let root =
647 + get_root_set_state(operand_id, &context.set_state_loads, &mut HashSet::new());
648 if let Some(root_id) = root {
649 if let Some(usages) = context.set_state_usages.get_mut(&root_id) {
650 usages.insert(LocKey::from_loc(&operand_loc));
@@ -665,8 +684,7 @@ fn record_instruction_derivations(
684 if operand.effect.is_mutable() {
685 if is_mutable_at(env, instr.id, operand.id) {
686 if let Some(existing) = context.derivation_cache.cache.get_mut(&operand.id) {
668 - existing.type_of_value =
669 - join_value(type_of_value, existing.type_of_value);
687 + existing.type_of_value = join_value(type_of_value, existing.type_of_value);
688 } else {
689 let name = identifiers[operand.id.0 as usize].name.clone();
690 context.derivation_cache.add_derivation_entry(
@@ -798,7 +816,15 @@ fn render_tree(
816 props_set: &mut indexmap::IndexSet<String>,
817 state_set: &mut indexmap::IndexSet<String>,
818 ) -> String {
801 - let prefix = format!("{}{}", indent, if is_last { "\u{2514}\u{2500}\u{2500} " } else { "\u{251c}\u{2500}\u{2500} " });
819 + let prefix = format!(
820 + "{}{}",
821 + indent,
822 + if is_last {
823 + "\u{2514}\u{2500}\u{2500} "
824 + } else {
825 + "\u{251c}\u{2500}\u{2500} "
826 + }
827 + );
828 let child_indent = format!("{}{}", indent, if is_last { " " } else { "\u{2502} " });
829
830 let mut result = format!("{}{}", prefix, node.name);
@@ -882,7 +908,11 @@ fn validate_effect(
908
909 // Consider setStates in the effect's dependency array as being part of effectSetStateUsages
910 for dep in dependencies {
885 - let root = get_root_set_state(dep.identifier, &context.set_state_loads, &mut HashSet::new());
911 + let root = get_root_set_state(
912 + dep.identifier,
913 + &context.set_state_loads,
914 + &mut HashSet::new(),
915 + );
916 if let Some(root_id) = root {
917 let mut set = HashSet::new();
918 set.insert(LocKey::from_loc(&dep.loc));
@@ -915,7 +945,8 @@ fn validate_effect(
945 let instr = &effect_function.instructions[instr_id.0 as usize];
946
947 // Early return if any instruction derives from a ref
918 - let lvalue_type = &types[identifiers[instr.lvalue.identifier.0 as usize].type_.0 as usize];
948 + let lvalue_type =
949 + &types[identifiers[instr.lvalue.identifier.0 as usize].type_.0 as usize];
950 if is_use_ref_type(lvalue_type) {
951 return;
952 }
@@ -948,9 +979,7 @@ fn validate_effect(
979 InstructionValue::CallExpression { callee, args, .. } => {
980 let callee_type =
981 &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];
951 - if is_set_state_type(callee_type)
952 - && args.len() == 1
953 - {
982 + if is_set_state_type(callee_type) && args.len() == 1 {
983 if let react_compiler_hir::PlaceOrSpread::Place(arg0) = &args[0] {
984 let callee_metadata =
985 context.derivation_cache.cache.get(&callee.identifier);
@@ -964,15 +993,17 @@ fn validate_effect(
993 continue;
994 }
995
967 - let arg_metadata =
968 - context.derivation_cache.cache.get(&arg0.identifier);
996 + let arg_metadata = context.derivation_cache.cache.get(&arg0.identifier);
997 if let Some(am) = arg_metadata {
998 // Get the user-visible identifier name, matching Babel's
999 // loc.identifierName. Falls back to extracting from source code.
1000 let callee_ident_name = get_identifier_name_with_loc(
973 - callee.identifier, identifiers, &callee.loc, env.code.as_deref(),
1001 + callee.identifier,
1002 + identifiers,
1003 + &callee.loc,
1004 + env.code.as_deref(),
1005 );
975 - effect_derived_set_state_calls.push(DerivedSetStateCall {
1006 + effect_derived_set_state_calls.push(DerivedSetStateCall {
1007 callee_loc: callee.loc,
1008 callee_id: callee.identifier,
1009 callee_identifier_name: callee_ident_name,
@@ -1061,7 +1092,10 @@ fn validate_effect(
1092
1093 // Check cleanup function dependencies
1094 let should_skip = if let Some(ref cleanup_deps) = cleanup_function_deps {
1064 - derived.source_ids.iter().any(|dep| cleanup_deps.contains(dep))
1095 + derived
1096 + .source_ids
1097 + .iter()
1098 + .any(|dep| cleanup_deps.contains(dep))
1099 } else {
1100 false
1101 };
@@ -1161,9 +1195,10 @@ pub fn validate_no_derived_computations_in_effects(
1195 if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) =
1196 (&args[0], &args[1])
1197 {
1164 - if let (Some(&func_id), Some(dep_elements)) =
1165 - (functions_map.get(&arg0.identifier), candidate_deps.get(&arg1.identifier))
1166 - {
1198 + if let (Some(&func_id), Some(dep_elements)) = (
1199 + functions_map.get(&arg0.identifier),
1200 + candidate_deps.get(&arg1.identifier),
1201 + ) {
1202 if !dep_elements.is_empty() {
1203 let resolved: Vec<IdentifierId> = dep_elements
1204 .iter()
@@ -1181,9 +1216,10 @@ pub fn validate_no_derived_computations_in_effects(
1216 if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) =
1217 (&args[0], &args[1])
1218 {
1184 - if let (Some(&func_id), Some(dep_elements)) =
1185 - (functions_map.get(&arg0.identifier), candidate_deps.get(&arg1.identifier))
1186 - {
1219 + if let (Some(&func_id), Some(dep_elements)) = (
1220 + functions_map.get(&arg0.identifier),
1221 + candidate_deps.get(&arg1.identifier),
1222 + ) {
1223 if !dep_elements.is_empty() {
1224 let resolved: Vec<IdentifierId> = dep_elements
1225 .iter()
@@ -1299,10 +1335,7 @@ fn validate_effect_non_exp(
1335 }
1336 }
1337 if !aggregate.is_empty() {
1302 - dep_values.insert(
1303 - instr.lvalue.identifier,
1304 - aggregate.into_iter().collect(),
1305 - );
1338 + dep_values.insert(instr.lvalue.identifier, aggregate.into_iter().collect());
1339 }
1340
1341 if let InstructionValue::CallExpression { callee, args, .. } = &instr.value {
@@ -1380,7 +1413,9 @@ fn validate_effect_non_exp(
1413 /// for resolving function expression context captures.
1414 fn non_exp_value_operands(value: &InstructionValue) -> Vec<IdentifierId> {
1415 match value {
1383 - InstructionValue::ComputedLoad { object, property, .. } => {
1416 + InstructionValue::ComputedLoad {
1417 + object, property, ..
1418 + } => {
1419 vec![object.identifier, property.identifier]
1420 }
1421 InstructionValue::PropertyLoad { object, .. } => vec![object.identifier],
compiler/crates/react_compiler_validation/src/validate_no_freezing_known_mutable_functions.rs
+3 -6
@@ -11,11 +11,11 @@ use react_compiler_diagnostics::{
11 CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,
12 };
13 use react_compiler_hir::environment::Environment;
14 +use react_compiler_hir::visitors::{each_instruction_value_operand, each_terminal_operand};
15 use react_compiler_hir::{
16 AliasingEffect, Effect, HirFunction, Identifier, IdentifierId, IdentifierName,
17 InstructionValue, Place, Type,
18 };
18 -use react_compiler_hir::visitors::{each_instruction_value_operand, each_terminal_operand};
19
20 /// Information about a known mutation effect: which identifier is mutated, and
21 /// the source location of the mutation.
@@ -76,8 +76,7 @@ fn check_no_freezing_known_mutable_functions(
76 let mutation_info = mutation_info.clone();
77 context_mutation_effects
78 .insert(instr.lvalue.identifier, mutation_info.clone());
79 - context_mutation_effects
80 - .insert(lvalue.place.identifier, mutation_info);
79 + context_mutation_effects.insert(lvalue.place.identifier, mutation_info);
80 }
81 }
82
@@ -122,9 +121,7 @@ fn check_no_freezing_known_mutable_functions(
121 }
122
123 AliasingEffect::MutateConditionally { value, .. }
125 - | AliasingEffect::MutateTransitiveConditionally {
126 - value, ..
127 - } => {
124 + | AliasingEffect::MutateTransitiveConditionally { value, .. } => {
125 // Only propagate existing known mutations for conditional effects
126 if let Some(known_mutation) =
127 context_mutation_effects.get(&value.identifier)
compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs
+85 -144
@@ -5,15 +5,13 @@ use react_compiler_diagnostics::{
5 };
6 use react_compiler_hir::environment::Environment;
7 use react_compiler_hir::object_shape::HookKind;
8 -use react_compiler_hir::{
9 - AliasingEffect, BlockId, HirFunction, Identifier, IdentifierId,
10 - InstructionValue, Place, Terminal,
11 - PrimitiveValue, PropertyLiteral, Type, UnaryOperator,
12 -};
8 use react_compiler_hir::visitors::{
9 each_instruction_value_operand as canonical_each_instruction_value_operand,
15 - each_terminal_operand,
16 - each_pattern_operand,
10 + each_pattern_operand, each_terminal_operand,
11 +};
12 +use react_compiler_hir::{
13 + AliasingEffect, BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Place,
14 + PrimitiveValue, PropertyLiteral, Terminal, Type, UnaryOperator,
15 };
16
17 const ERROR_DESCRIPTION: &str = "React refs are values that are not needed for rendering. \
@@ -170,7 +168,10 @@ impl RefAccessType {
168
169 fn join_ref_access_ref_types(a: &RefAccessRefType, b: &RefAccessRefType) -> RefAccessRefType {
170 match (a, b) {
173 - (RefAccessRefType::RefValue { ref_id: a_id, .. }, RefAccessRefType::RefValue { ref_id: b_id, .. }) => {
171 + (
172 + RefAccessRefType::RefValue { ref_id: a_id, .. },
173 + RefAccessRefType::RefValue { ref_id: b_id, .. },
174 + ) => {
175 if a_id == b_id {
176 a.clone()
177 } else {
@@ -249,15 +250,13 @@ fn join_ref_access_types(a: &RefAccessType, b: &RefAccessType) -> RefAccessType
250 other.clone()
251 }
252 (RefAccessType::Nullable, other) | (other, RefAccessType::Nullable) => other.clone(),
252 - _ => {
253 - match (a.to_ref_type(), b.to_ref_type()) {
254 - (Some(a_ref), Some(b_ref)) => {
255 - RefAccessType::from_ref_type(&join_ref_access_ref_types(&a_ref, &b_ref))
256 - }
257 - (Some(r), None) | (None, Some(r)) => RefAccessType::from_ref_type(&r),
258 - _ => RefAccessType::None,
253 + _ => match (a.to_ref_type(), b.to_ref_type()) {
254 + (Some(a_ref), Some(b_ref)) => {
255 + RefAccessType::from_ref_type(&join_ref_access_ref_types(&a_ref, &b_ref))
256 }
260 - }
257 + (Some(r), None) | (None, Some(r)) => RefAccessType::from_ref_type(&r),
258 + _ => RefAccessType::None,
259 + },
260 }
261 }
262
@@ -312,10 +311,7 @@ impl Env {
311 .map(|p| p.identifier)
312 .unwrap_or(key);
313 let current = self.data.get(&operand_id);
315 - let widened_value = join_ref_access_types(
316 - &value,
317 - current.unwrap_or(&RefAccessType::None),
318 - );
314 + let widened_value = join_ref_access_types(&value, current.unwrap_or(&RefAccessType::None));
315 if current.is_none() && widened_value == RefAccessType::None {
316 // No change needed
317 } else if current.map_or(true, |c| c != &widened_value) {
@@ -327,11 +323,7 @@ impl Env {
323
324 // --- Helper functions ---
325
330 -fn ref_type_of_type(
331 - id: IdentifierId,
332 - identifiers: &[Identifier],
333 - types: &[Type],
334 -) -> RefAccessType {
326 +fn ref_type_of_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> RefAccessType {
327 let identifier = &identifiers[id.0 as usize];
328 let ty = &types[identifier.type_.0 as usize];
329 if react_compiler_hir::is_ref_value_type(ty) {
@@ -393,11 +385,7 @@ fn validate_no_direct_ref_value_access(
385 }
386 }
387
396 -fn validate_no_ref_value_access(
397 - errors: &mut Vec<CompilerDiagnostic>,
398 - env: &Env,
399 - operand: &Place,
400 -) {
388 +fn validate_no_ref_value_access(errors: &mut Vec<CompilerDiagnostic>, env: &Env, operand: &Place) {
389 if let Some(ty) = env.get(operand.identifier) {
390 let ty = destructure(ty);
391 match &ty {
@@ -410,9 +398,7 @@ fn validate_no_ref_value_access(
398 )
399 .with_detail(CompilerDiagnosticDetail::Error {
400 loc: loc.or(operand.loc),
413 - message: Some(
414 - "Cannot access ref value during render".to_string(),
415 - ),
401 + message: Some("Cannot access ref value during render".to_string()),
402 identifier_name: None,
403 }),
404 );
@@ -429,9 +415,7 @@ fn validate_no_ref_value_access(
415 )
416 .with_detail(CompilerDiagnosticDetail::Error {
417 loc: operand.loc,
432 - message: Some(
433 - "Cannot access ref value during render".to_string(),
434 - ),
418 + message: Some("Cannot access ref value during render".to_string()),
419 identifier_name: None,
420 }),
421 );
@@ -451,10 +435,7 @@ fn validate_no_ref_passed_to_function(
435 let ty = destructure(ty);
436 match &ty {
437 RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => {
454 - let error_loc = if let RefAccessType::RefValue {
455 - loc: ref_loc, ..
456 - } = &ty
457 - {
438 + let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty {
439 ref_loc.or(loc)
440 } else {
441 loc
@@ -510,10 +491,7 @@ fn validate_no_ref_update(
491 let ty = destructure(ty);
492 match &ty {
493 RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => {
513 - let error_loc = if let RefAccessType::RefValue {
514 - loc: ref_loc, ..
515 - } = &ty
516 - {
494 + let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty {
495 ref_loc.or(loc)
496 } else {
497 loc
@@ -537,7 +515,10 @@ fn validate_no_ref_update(
515 }
516
517 fn guard_check(errors: &mut Vec<CompilerDiagnostic>, operand: &Place, env: &Env) {
540 - if matches!(env.get(operand.identifier), Some(RefAccessType::Guard { .. })) {
518 + if matches!(
519 + env.get(operand.identifier),
520 + Some(RefAccessType::Guard { .. })
521 + ) {
522 errors.push(
523 CompilerDiagnostic::new(
524 ErrorCategory::Refs,
@@ -701,7 +682,8 @@ fn validate_no_ref_access_in_render_impl(
682 match &instr.value {
683 InstructionValue::JsxExpression { .. }
684 | InstructionValue::JsxFragment { .. } => {
704 - for operand in &canonical_each_instruction_value_operand(&instr.value, env) {
685 + for operand in &canonical_each_instruction_value_operand(&instr.value, env)
686 + {
687 validate_no_direct_ref_value_access(errors, operand, ref_env);
688 }
689 }
@@ -714,12 +696,10 @@ fn validate_no_ref_access_in_render_impl(
696 Some(RefAccessType::Structure {
697 value: Some(value), ..
698 }) => Some(RefAccessType::from_ref_type(value)),
717 - Some(RefAccessType::Ref { ref_id }) => {
718 - Some(RefAccessType::RefValue {
719 - loc: instr.loc,
720 - ref_id: Some(*ref_id),
721 - })
722 - }
699 + Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue {
700 + loc: instr.loc,
701 + ref_id: Some(*ref_id),
702 + }),
703 _ => None,
704 };
705 ref_env.set(
@@ -735,12 +715,10 @@ fn validate_no_ref_access_in_render_impl(
715 Some(RefAccessType::Structure {
716 value: Some(value), ..
717 }) => Some(RefAccessType::from_ref_type(value)),
738 - Some(RefAccessType::Ref { ref_id }) => {
739 - Some(RefAccessType::RefValue {
740 - loc: instr.loc,
741 - ref_id: Some(*ref_id),
742 - })
743 - }
718 + Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue {
719 + loc: instr.loc,
720 + ref_id: Some(*ref_id),
721 + }),
722 _ => None,
723 };
724 ref_env.set(
@@ -753,45 +731,33 @@ fn validate_no_ref_access_in_render_impl(
731 InstructionValue::TypeCastExpression { value, .. } => {
732 ref_env.set(
733 instr.lvalue.identifier,
756 - ref_env
757 - .get(value.identifier)
758 - .cloned()
759 - .unwrap_or_else(|| {
760 - ref_type_of_type(instr.lvalue.identifier, identifiers, types)
761 - }),
734 + ref_env.get(value.identifier).cloned().unwrap_or_else(|| {
735 + ref_type_of_type(instr.lvalue.identifier, identifiers, types)
736 + }),
737 );
738 }
739 InstructionValue::LoadContext { place, .. }
740 | InstructionValue::LoadLocal { place, .. } => {
741 ref_env.set(
742 instr.lvalue.identifier,
768 - ref_env
769 - .get(place.identifier)
770 - .cloned()
771 - .unwrap_or_else(|| {
772 - ref_type_of_type(instr.lvalue.identifier, identifiers, types)
773 - }),
743 + ref_env.get(place.identifier).cloned().unwrap_or_else(|| {
744 + ref_type_of_type(instr.lvalue.identifier, identifiers, types)
745 + }),
746 );
747 }
748 InstructionValue::StoreContext { lvalue, value, .. }
749 | InstructionValue::StoreLocal { lvalue, value, .. } => {
750 ref_env.set(
751 lvalue.place.identifier,
780 - ref_env
781 - .get(value.identifier)
782 - .cloned()
783 - .unwrap_or_else(|| {
784 - ref_type_of_type(lvalue.place.identifier, identifiers, types)
785 - }),
752 + ref_env.get(value.identifier).cloned().unwrap_or_else(|| {
753 + ref_type_of_type(lvalue.place.identifier, identifiers, types)
754 + }),
755 );
756 ref_env.set(
757 instr.lvalue.identifier,
789 - ref_env
790 - .get(value.identifier)
791 - .cloned()
792 - .unwrap_or_else(|| {
793 - ref_type_of_type(instr.lvalue.identifier, identifiers, types)
794 - }),
758 + ref_env.get(value.identifier).cloned().unwrap_or_else(|| {
759 + ref_type_of_type(instr.lvalue.identifier, identifiers, types)
760 + }),
761 );
762 }
763 InstructionValue::Destructure { value, lvalue, .. } => {
@@ -812,11 +778,7 @@ fn validate_no_ref_access_in_render_impl(
778 ref_env.set(
779 pattern_place.identifier,
780 lookup_type.clone().unwrap_or_else(|| {
815 - ref_type_of_type(
816 - pattern_place.identifier,
817 - identifiers,
818 - types,
819 - )
781 + ref_type_of_type(pattern_place.identifier, identifiers, types)
782 }),
783 );
784 }
@@ -873,13 +835,15 @@ fn validate_no_ref_access_in_render_impl(
835 "Cannot access refs during render",
836 Some(ERROR_DESCRIPTION.to_string()),
837 )
876 - .with_detail(CompilerDiagnosticDetail::Error {
877 - loc: callee.loc,
878 - message: Some(
879 - "This function accesses a ref value".to_string(),
880 - ),
881 - identifier_name: None,
882 - }),
838 + .with_detail(
839 + CompilerDiagnosticDetail::Error {
840 + loc: callee.loc,
841 + message: Some(
842 + "This function accesses a ref value".to_string(),
843 + ),
844 + identifier_name: None,
845 + },
846 + ),
847 );
848 }
849 }
@@ -891,10 +855,8 @@ fn validate_no_ref_access_in_render_impl(
855 if !did_error {
856 let is_ref_lvalue =
857 is_ref_type(instr.lvalue.identifier, identifiers, types);
894 - let callee_identifier =
895 - &identifiers[callee.identifier.0 as usize];
896 - let callee_type =
897 - &types[callee_identifier.type_.0 as usize];
858 + let callee_identifier = &identifiers[callee.identifier.0 as usize];
859 + let callee_type = &types[callee_identifier.type_.0 as usize];
860 let hook_kind = env.get_hook_kind_for_type(callee_type).ok().flatten();
861
862 if is_ref_lvalue
@@ -902,21 +864,19 @@ fn validate_no_ref_access_in_render_impl(
864 && !matches!(hook_kind, Some(&HookKind::UseState))
865 && !matches!(hook_kind, Some(&HookKind::UseReducer)))
866 {
905 - for operand in &canonical_each_instruction_value_operand(&instr.value, env)
867 + for operand in
868 + &canonical_each_instruction_value_operand(&instr.value, env)
869 {
870 /*
871 * Allow passing refs or ref-accessing functions when:
872 * 1. lvalue is a ref (mergeRefs pattern)
873 * 2. calling hooks (independently validated)
874 */
912 - validate_no_direct_ref_value_access(
913 - errors, operand, ref_env,
914 - );
875 + validate_no_direct_ref_value_access(errors, operand, ref_env);
876 }
916 - } else if interpolated_as_jsx
917 - .contains(&instr.lvalue.identifier)
918 - {
919 - for operand in &canonical_each_instruction_value_operand(&instr.value, env)
877 + } else if interpolated_as_jsx.contains(&instr.lvalue.identifier) {
878 + for operand in
879 + &canonical_each_instruction_value_operand(&instr.value, env)
880 {
881 /*
882 * Special case: the lvalue is passed as a jsx child
@@ -930,17 +890,14 @@ fn validate_no_ref_access_in_render_impl(
890 * use the effects to determine what validation to apply.
891 * Track visited id:kind pairs to avoid duplicate errors.
892 */
933 - let mut visited_effects: HashSet<String> =
934 - HashSet::new();
893 + let mut visited_effects: HashSet<String> = HashSet::new();
894 for effect in effects {
895 let (place, validation) = match effect {
896 AliasingEffect::Freeze { value, .. } => {
897 (Some(value), "direct-ref")
898 }
899 AliasingEffect::Mutate { value, .. }
941 - | AliasingEffect::MutateTransitive {
942 - value, ..
943 - }
900 + | AliasingEffect::MutateTransitive { value, .. }
901 | AliasingEffect::MutateConditionally {
902 value, ..
903 }
@@ -958,9 +915,7 @@ fn validate_no_ref_access_in_render_impl(
915 | AliasingEffect::CreateFrom { from, .. } => {
916 (Some(from), "ref-passed")
917 }
961 - AliasingEffect::ImmutableCapture {
962 - from, ..
963 - } => {
918 + AliasingEffect::ImmutableCapture { from, .. } => {
919 /*
920 * ImmutableCapture: check whether the same
921 * operand also has a Freeze effect to
@@ -1071,9 +1026,7 @@ fn validate_no_ref_access_in_render_impl(
1026 let mut found_safe = false;
1027 if matches!(&instr.value, InstructionValue::PropertyStore { .. }) {
1028 if let Some(RefAccessType::Ref { ref_id }) = &target {
1074 - if let Some(pos) = safe_blocks
1075 - .iter()
1076 - .position(|(_, r)| r == ref_id)
1029 + if let Some(pos) = safe_blocks.iter().position(|(_, r)| r == ref_id)
1030 {
1031 safe_blocks.remove(pos);
1032 found_safe = true;
@@ -1098,8 +1051,7 @@ fn validate_no_ref_access_in_render_impl(
1051 if let Some(RefAccessType::Structure { .. }) = &value_type {
1052 let mut object_type = value_type.unwrap();
1053 if let Some(t) = &target {
1101 - object_type =
1102 - join_ref_access_types(&object_type, t);
1054 + object_type = join_ref_access_types(&object_type, t);
1055 }
1056 ref_env.set(object.identifier, object_type);
1057 }
@@ -1111,17 +1063,12 @@ fn validate_no_ref_access_in_render_impl(
1063 | InstructionValue::FinishMemoize { .. } => {}
1064 InstructionValue::LoadGlobal { binding, .. } => {
1065 if binding.name() == "undefined" {
1114 - ref_env
1115 - .set(instr.lvalue.identifier, RefAccessType::Nullable);
1066 + ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable);
1067 }
1068 }
1069 InstructionValue::Primitive { value, .. } => {
1119 - if matches!(
1120 - value,
1121 - PrimitiveValue::Null | PrimitiveValue::Undefined
1122 - ) {
1123 - ref_env
1124 - .set(instr.lvalue.identifier, RefAccessType::Nullable);
1070 + if matches!(value, PrimitiveValue::Null | PrimitiveValue::Undefined) {
1071 + ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable);
1072 }
1073 }
1074 InstructionValue::UnaryExpression {
@@ -1167,9 +1114,7 @@ fn validate_no_ref_access_in_render_impl(
1114 validate_no_ref_value_access(errors, ref_env, value);
1115 }
1116 }
1170 - InstructionValue::BinaryExpression {
1171 - left, right, ..
1172 - } => {
1117 + InstructionValue::BinaryExpression { left, right, .. } => {
1118 let left_type = ref_env.get(left.identifier).cloned();
1119 let right_type = ref_env.get(right.identifier).cloned();
1120 let mut nullish = false;
@@ -1195,10 +1140,8 @@ fn validate_no_ref_access_in_render_impl(
1140
1141 if let Some(ref_id) = found_ref_id {
1142 if nullish {
1198 - ref_env.set(
1199 - instr.lvalue.identifier,
1200 - RefAccessType::Guard { ref_id },
1201 - );
1143 + ref_env
1144 + .set(instr.lvalue.identifier, RefAccessType::Guard { ref_id });
1145 } else {
1146 validate_no_ref_value_access(errors, ref_env, left);
1147 validate_no_ref_value_access(errors, ref_env, right);
@@ -1209,7 +1152,8 @@ fn validate_no_ref_access_in_render_impl(
1152 }
1153 }
1154 _ => {
1212 - for operand in &canonical_each_instruction_value_operand(&instr.value, env) {
1155 + for operand in &canonical_each_instruction_value_operand(&instr.value, env)
1156 + {
1157 validate_no_ref_value_access(errors, ref_env, operand);
1158 }
1159 }
@@ -1269,8 +1213,7 @@ fn validate_no_ref_access_in_render_impl(
1213 test, fallthrough, ..
1214 } = &block.terminal
1215 {
1272 - if let Some(RefAccessType::Guard { ref_id }) = ref_env.get(test.identifier)
1273 - {
1216 + if let Some(RefAccessType::Guard { ref_id }) = ref_env.get(test.identifier) {
1217 if !safe_blocks.iter().any(|(_, r)| r == ref_id) {
1218 safe_blocks.push((*fallthrough, *ref_id));
1219 }
@@ -1301,13 +1244,11 @@ fn validate_no_ref_access_in_render_impl(
1244 }
1245
1246 if ref_env.has_changed() {
1304 - errors.push(
1305 - CompilerDiagnostic::new(
1306 - react_compiler_diagnostics::ErrorCategory::Invariant,
1307 - "Ref type environment did not converge",
1308 - None,
1309 - )
1310 - );
1247 + errors.push(CompilerDiagnostic::new(
1248 + react_compiler_diagnostics::ErrorCategory::Invariant,
1249 + "Ref type environment did not converge",
1250 + None,
1251 + ));
1252 return RefAccessType::None;
1253 }
1254
compiler/crates/react_compiler_validation/src/validate_no_set_state_in_effects.rs
+34 -43
@@ -20,10 +20,10 @@ use react_compiler_diagnostics::{
20 use react_compiler_hir::dominator::{compute_post_dominator_tree, post_dominator_frontier};
21 use react_compiler_hir::environment::Environment;
22 use react_compiler_hir::{
23 - is_ref_value_type, is_set_state_type, is_use_effect_event_type, is_use_effect_hook_type,
24 - is_use_insertion_effect_hook_type, is_use_layout_effect_hook_type, is_use_ref_type,
25 - BlockId, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue, PlaceOrSpread,
26 - PropertyLiteral, SourceLocation, Terminal, Type, visitors,
23 + BlockId, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue,
24 + PlaceOrSpread, PropertyLiteral, SourceLocation, Terminal, Type, is_ref_value_type,
25 + is_set_state_type, is_use_effect_event_type, is_use_effect_hook_type,
26 + is_use_insertion_effect_hook_type, is_use_layout_effect_hook_type, is_use_ref_type, visitors,
27 };
28
29 pub fn validate_no_set_state_in_effects(
@@ -81,10 +81,9 @@ pub fn validate_no_set_state_in_effects(
81 }
82 }
83 }
84 - InstructionValue::MethodCall {
85 - property, args, ..
86 - } => {
87 - let prop_type = &types[identifiers[property.identifier.0 as usize].type_.0 as usize];
84 + InstructionValue::MethodCall { property, args, .. } => {
85 + let prop_type =
86 + &types[identifiers[property.identifier.0 as usize].type_.0 as usize];
87 if is_use_effect_event_type(prop_type) {
88 if let Some(first_arg) = args.first() {
89 if let PlaceOrSpread::Place(arg_place) = first_arg {
@@ -100,9 +99,7 @@ pub fn validate_no_set_state_in_effects(
99 {
100 if let Some(first_arg) = args.first() {
101 if let PlaceOrSpread::Place(arg_place) = first_arg {
103 - if let Some(info) =
104 - set_state_functions.get(&arg_place.identifier)
105 - {
102 + if let Some(info) = set_state_functions.get(&arg_place.identifier) {
103 push_error(&mut errors, info, enable_verbose);
104 }
105 }
@@ -110,7 +107,8 @@ pub fn validate_no_set_state_in_effects(
107 }
108 }
109 InstructionValue::CallExpression { callee, args, .. } => {
113 - let callee_type = &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];
110 + let callee_type =
111 + &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];
112 if is_use_effect_event_type(callee_type) {
113 if let Some(first_arg) = args.first() {
114 if let PlaceOrSpread::Place(arg_place) = first_arg {
@@ -126,9 +124,7 @@ pub fn validate_no_set_state_in_effects(
124 {
125 if let Some(first_arg) = args.first() {
126 if let PlaceOrSpread::Place(arg_place) = first_arg {
129 - if let Some(info) =
130 - set_state_functions.get(&arg_place.identifier)
131 - {
127 + if let Some(info) = set_state_functions.get(&arg_place.identifier) {
128 push_error(&mut errors, info, enable_verbose);
129 }
130 }
@@ -170,7 +166,11 @@ fn get_identifier_name_with_loc(
166 let end_idx = loc.end.index? as usize;
167 if start_idx < code.len() && end_idx <= code.len() && start_idx < end_idx {
168 let slice = &code[start_idx..end_idx];
173 - if !slice.is_empty() && slice.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '$') {
169 + if !slice.is_empty()
170 + && slice
171 + .chars()
172 + .all(|c| c.is_alphanumeric() || c == '_' || c == '$')
173 + {
174 return Some(slice.to_string());
175 }
176 }
@@ -321,23 +321,15 @@ fn create_ref_controlled_block_checker(
321 let control_block = &func.body.blocks[frontier_block_id];
322 match &control_block.terminal {
323 Terminal::If { test, .. } | Terminal::Branch { test, .. } => {
324 - if is_derived_from_ref(
325 - test.identifier,
326 - ref_derived_values,
327 - identifiers,
328 - types,
329 - ) {
324 + if is_derived_from_ref(test.identifier, ref_derived_values, identifiers, types)
325 + {
326 is_controlled = true;
327 break;
328 }
329 }
330 Terminal::Switch { test, cases, .. } => {
335 - if is_derived_from_ref(
336 - test.identifier,
337 - ref_derived_values,
338 - identifiers,
339 - types,
340 - ) {
331 + if is_derived_from_ref(test.identifier, ref_derived_values, identifiers, types)
332 + {
333 is_controlled = true;
334 break;
335 }
@@ -389,12 +381,7 @@ fn get_set_state_call(
381 for (_block_id, block) in &func.body.blocks {
382 for phi in &block.phis {
383 let is_phi_derived = phi.operands.values().any(|operand| {
392 - is_derived_from_ref(
393 - operand.identifier,
394 - &ref_derived_values,
395 - identifiers,
396 - types,
397 - )
384 + is_derived_from_ref(operand.identifier, &ref_derived_values, identifiers, types)
385 });
386 if is_phi_derived {
387 ref_derived_values.insert(phi.place.identifier);
@@ -449,7 +436,10 @@ fn get_set_state_call(
436 };
437
438 let is_ref_controlled_block = |block_id: BlockId| -> bool {
452 - ref_controlled_blocks.get(&block_id).copied().unwrap_or(false)
439 + ref_controlled_blocks
440 + .get(&block_id)
441 + .copied()
442 + .unwrap_or(false)
443 };
444
445 // Reset and redo: second pass with control dominator info available
@@ -468,12 +458,7 @@ fn get_set_state_call(
458 continue;
459 }
460 let is_phi_derived = phi.operands.values().any(|operand| {
471 - is_derived_from_ref(
472 - operand.identifier,
473 - &ref_derived_values,
474 - identifiers,
475 - types,
476 - )
461 + is_derived_from_ref(operand.identifier, &ref_derived_values, identifiers, types)
462 });
463 if is_phi_derived {
464 ref_derived_values.insert(phi.place.identifier);
@@ -573,9 +558,15 @@ fn get_set_state_call(
558 // loc.identifierName behavior. Uses declaration_id to find
559 // the original named identifier when SSA creates unnamed copies.
560 let callee_name = get_identifier_name_with_loc(
576 - callee.identifier, identifiers, &callee.loc, source_code,
561 + callee.identifier,
562 + identifiers,
563 + &callee.loc,
564 + source_code,
565 );
578 - return Ok(Some(SetStateInfo { loc: callee.loc, identifier_name: callee_name }));
566 + return Ok(Some(SetStateInfo {
567 + loc: callee.loc,
568 + identifier_name: callee_name,
569 + }));
570 }
571 }
572 _ => {}
compiler/crates/react_compiler_validation/src/validate_no_set_state_in_render.rs
+13 -23
@@ -9,16 +9,15 @@
9
10 use std::collections::HashSet;
11
12 -use react_compiler_diagnostics::{
13 - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory,
14 -};
12 +use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory};
13 use react_compiler_hir::dominator::compute_unconditional_blocks;
14 use react_compiler_hir::environment::Environment;
17 -use react_compiler_hir::{
18 - BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Type,
19 -};
15 +use react_compiler_hir::{BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Type};
16
21 -pub fn validate_no_set_state_in_render(func: &HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
17 +pub fn validate_no_set_state_in_render(
18 + func: &HirFunction,
19 + env: &mut Environment,
20 +) -> Result<(), CompilerDiagnostic> {
21 let mut unconditional_set_state_functions: HashSet<IdentifierId> = HashSet::new();
22 let next_block_id = env.next_block_id().0;
23 let diagnostics = validate_impl(
@@ -71,10 +70,8 @@ fn validate_impl(
70 }
71 InstructionValue::StoreLocal { lvalue, value, .. } => {
72 if unconditional_set_state_functions.contains(&value.identifier) {
74 - unconditional_set_state_functions
75 - .insert(lvalue.place.identifier);
76 - unconditional_set_state_functions
77 - .insert(instr.lvalue.identifier);
73 + unconditional_set_state_functions.insert(lvalue.place.identifier);
74 + unconditional_set_state_functions.insert(instr.lvalue.identifier);
75 }
76 }
77 InstructionValue::ObjectMethod { lowered_func, .. }
@@ -85,8 +82,7 @@ fn validate_impl(
82 // For FunctionExpression/ObjectMethod, operands are the context captures.
83 let has_set_state_operand = inner_func.context.iter().any(|ctx_place| {
84 is_set_state_id(ctx_place.identifier, identifiers, types)
88 - || unconditional_set_state_functions
89 - .contains(&ctx_place.identifier)
85 + || unconditional_set_state_functions.contains(&ctx_place.identifier)
86 });
87
88 if has_set_state_operand {
@@ -100,23 +96,18 @@ fn validate_impl(
96 unconditional_set_state_functions,
97 )?;
98 if !inner_errors.is_empty() {
103 - unconditional_set_state_functions
104 - .insert(instr.lvalue.identifier);
99 + unconditional_set_state_functions.insert(instr.lvalue.identifier);
100 }
101 }
102 }
108 - InstructionValue::StartMemoize {
109 - manual_memo_id, ..
110 - } => {
103 + InstructionValue::StartMemoize { manual_memo_id, .. } => {
104 assert!(
105 active_manual_memo_id.is_none(),
106 "Unexpected nested StartMemoize instructions"
107 );
108 active_manual_memo_id = Some(*manual_memo_id);
109 }
117 - InstructionValue::FinishMemoize {
118 - manual_memo_id, ..
119 - } => {
110 + InstructionValue::FinishMemoize { manual_memo_id, .. } => {
111 assert!(
112 active_manual_memo_id == Some(*manual_memo_id),
113 "Expected FinishMemoize to align with previous StartMemoize instruction"
@@ -125,8 +116,7 @@ fn validate_impl(
116 }
117 InstructionValue::CallExpression { callee, .. } => {
118 if is_set_state_id(callee.identifier, identifiers, types)
128 - || unconditional_set_state_functions
129 - .contains(&callee.identifier)
119 + || unconditional_set_state_functions.contains(&callee.identifier)
120 {
121 if active_manual_memo_id.is_some() {
122 errors.push(
compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs
+57 -64
@@ -14,13 +14,13 @@ use std::collections::{HashMap, HashSet};
14 use react_compiler_diagnostics::{
15 CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,
16 };
17 +use react_compiler_hir::environment::Environment;
18 use react_compiler_hir::{
18 - DeclarationId, DependencyPathEntry, IdentifierId, InstructionKind, InstructionValue,
19 - ManualMemoDependency, ManualMemoDependencyRoot, Place, ReactiveBlock, ReactiveFunction,
20 - ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue, ScopeId,
21 - IdentifierName, Identifier,
19 + DeclarationId, DependencyPathEntry, Identifier, IdentifierId, IdentifierName, InstructionKind,
20 + InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot, Place, ReactiveBlock,
21 + ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue,
22 + ScopeId,
23 };
23 -use react_compiler_hir::environment::Environment;
24
25 /// State tracked during manual memo validation within a StartMemoize..FinishMemoize range.
26 struct ManualMemoBlockState {
@@ -55,10 +55,7 @@ struct VisitorState<'a> {
55 /// 1. Dependencies' scopes have completed before the memo block starts
56 /// 2. Memoized values are actually within scopes (not unmemoized)
57 /// 3. Inferred scope dependencies match the source dependencies
58 -pub fn validate_preserved_manual_memoization(
59 - func: &ReactiveFunction,
60 - env: &mut Environment,
61 -) {
58 +pub fn validate_preserved_manual_memoization(func: &ReactiveFunction, env: &mut Environment) {
59 let mut state = VisitorState {
60 env,
61 manual_memo_state: None,
@@ -129,9 +126,7 @@ fn visit_terminal(
126 ReactiveTerminal::Label { block, .. } => {
127 visit_block(block, state);
128 }
132 - ReactiveTerminal::Try {
133 - block, handler, ..
134 - } => {
129 + ReactiveTerminal::Try { block, handler, .. } => {
130 visit_block(block, state);
131 visit_block(handler, state);
132 }
@@ -220,8 +215,7 @@ fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) {
215 for place in &operand_places {
216 let ident = &state.env.identifiers[place.identifier.0 as usize];
217 if let Some(scope_id) = ident.scope {
223 - if !state.scopes.contains(&scope_id)
224 - && !state.pruned_scopes.contains(&scope_id)
218 + if !state.scopes.contains(&scope_id) && !state.pruned_scopes.contains(&scope_id)
219 {
220 let diag = CompilerDiagnostic::new(
221 ErrorCategory::PreserveManualMemo,
@@ -255,7 +249,11 @@ fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) {
249 }
250
251 // TS: CompilerError.invariant(state.manualMemoState.manualMemoId === value.manualMemoId, ...)
258 - if state.manual_memo_state.as_ref().map_or(true, |s| s.manual_memo_id != *manual_memo_id) {
252 + if state
253 + .manual_memo_state
254 + .as_ref()
255 + .map_or(true, |s| s.manual_memo_id != *manual_memo_id)
256 + {
257 state.manual_memo_state = None;
258 return;
259 }
@@ -287,11 +285,7 @@ fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) {
285 }
286 }
287 }
290 - ReactiveValue::Instruction(InstructionValue::StoreLocal {
291 - lvalue,
292 - value,
293 - ..
294 - }) => {
288 + ReactiveValue::Instruction(InstructionValue::StoreLocal { lvalue, value, .. }) => {
289 // Track reassignments from inlining of manual memo
290 if state.manual_memo_state.is_some() && lvalue.kind == InstructionKind::Reassign {
291 let decl_id =
@@ -431,8 +425,7 @@ fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) {
425 InstructionValue::StoreLocal { lvalue, .. }
426 | InstructionValue::StoreContext { lvalue, .. } => {
427 if let Some(ref mut memo_state) = state.manual_memo_state {
434 - let ident =
435 - &state.env.identifiers[lvalue.place.identifier.0 as usize];
428 + let ident = &state.env.identifiers[lvalue.place.identifier.0 as usize];
429 memo_state.decls.insert(ident.declaration_id);
430 if is_named(ident) {
431 state.temporaries.insert(
@@ -452,8 +445,7 @@ fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) {
445 InstructionValue::Destructure { lvalue, .. } => {
446 if let Some(ref mut memo_state) = state.manual_memo_state {
447 for place in destructure_lvalue_places(&lvalue.pattern) {
455 - let ident =
456 - &state.env.identifiers[place.identifier.0 as usize];
448 + let ident = &state.env.identifiers[place.identifier.0 as usize];
449 memo_state.decls.insert(ident.declaration_id);
450 if is_named(ident) {
451 state.temporaries.insert(
@@ -556,12 +548,8 @@ fn compare_deps(
548 ) -> CompareDependencyResult {
549 let roots_equal = match (&inferred.root, &source.root) {
550 (
559 - ManualMemoDependencyRoot::Global {
560 - identifier_name: a,
561 - },
562 - ManualMemoDependencyRoot::Global {
563 - identifier_name: b,
564 - },
551 + ManualMemoDependencyRoot::Global { identifier_name: a },
552 + ManualMemoDependencyRoot::Global { identifier_name: b },
553 ) => a == b,
554 (
555 ManualMemoDependencyRoot::NamedLocal { value: a, .. },
@@ -587,13 +575,17 @@ fn compare_deps(
575 if is_subpath
576 && (source.path.len() == inferred.path.len()
577 || (inferred.path.len() >= source.path.len()
590 - && !inferred.path.iter().any(|t| t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()))))
578 + && !inferred.path.iter().any(|t| {
579 + t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())
580 + })))
581 {
582 CompareDependencyResult::Ok
583 } else if is_subpath {
594 - if source.path.iter().any(|t| t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()))
595 - || inferred.path.iter().any(|t| t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()))
596 - {
584 + if source.path.iter().any(|t| {
585 + t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())
586 + }) || inferred.path.iter().any(|t| {
587 + t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())
588 + }) {
589 CompareDependencyResult::RefAccessDifference
590 } else {
591 CompareDependencyResult::Subpath
@@ -615,17 +607,20 @@ fn pretty_print_scope_dependency(
607 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),
608 None => "[unnamed]".to_string(),
609 };
618 - let path_str: String = dep_path.iter().map(|entry| {
619 - let prop = match &entry.property {
620 - react_compiler_hir::PropertyLiteral::String(s) => s.clone(),
621 - react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),
622 - };
623 - if entry.optional {
624 - format!("?.{}", prop)
625 - } else {
626 - format!(".{}", prop)
627 - }
628 - }).collect();
610 + let path_str: String = dep_path
611 + .iter()
612 + .map(|entry| {
613 + let prop = match &entry.property {
614 + react_compiler_hir::PropertyLiteral::String(s) => s.clone(),
615 + react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),
616 + };
617 + if entry.optional {
618 + format!("?.{}", prop)
619 + } else {
620 + format!(".{}", prop)
621 + }
622 + })
623 + .collect();
624 format!("{}{}", root_str, path_str)
625 }
626
@@ -646,23 +641,25 @@ fn print_manual_memo_dependency(
641 }
642 ManualMemoDependencyRoot::Global { identifier_name } => identifier_name.clone(),
643 };
649 - let path_str: String = dep.path.iter().map(|entry| {
650 - let prop = match &entry.property {
651 - react_compiler_hir::PropertyLiteral::String(s) => s.clone(),
652 - react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),
653 - };
654 - if with_optional && entry.optional {
655 - format!("?.{}", prop)
656 - } else {
657 - format!(".{}", prop)
658 - }
659 - }).collect();
644 + let path_str: String = dep
645 + .path
646 + .iter()
647 + .map(|entry| {
648 + let prop = match &entry.property {
649 + react_compiler_hir::PropertyLiteral::String(s) => s.clone(),
650 + react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),
651 + };
652 + if with_optional && entry.optional {
653 + format!("?.{}", prop)
654 + } else {
655 + format!(".{}", prop)
656 + }
657 + })
658 + .collect();
659 format!("{}{}", root_str, path_str)
660 }
661
663 -fn get_compare_dependency_result_description(
664 - result: CompareDependencyResult,
665 -) -> &'static str {
662 +fn get_compare_dependency_result_description(result: CompareDependencyResult) -> &'static str {
663 match result {
664 CompareDependencyResult::Ok => "Dependencies equal",
665 CompareDependencyResult::RootDifference | CompareDependencyResult::PathDifference => {
@@ -739,11 +736,7 @@ fn validate_inferred_dep(
736
737 let extra = if is_named(ident) {
738 // Use the original dep_id/dep_path (matching TS prettyPrintScopeDependency(dep))
742 - let dep_str = pretty_print_scope_dependency(
743 - dep_id,
744 - dep_path,
745 - &env.identifiers,
746 - );
739 + let dep_str = pretty_print_scope_dependency(dep_id, dep_path, &env.identifiers);
740 let source_deps_str: String = valid_deps_in_memo_block
741 .iter()
742 .map(|d| print_manual_memo_dependency(d, &env.identifiers, true))
compiler/crates/react_compiler_validation/src/validate_static_components.rs
+1 -2
@@ -65,8 +65,7 @@ pub fn validate_static_components(func: &HirFunction) -> CompilerError {
65 }
66 InstructionValue::JsxExpression { tag, .. } => {
67 if let JsxTag::Place(tag_place) = tag {
68 - if let Some(location) =
69 - known_dynamic_components.get(&tag_place.identifier)
68 + if let Some(location) = known_dynamic_components.get(&tag_place.identifier)
69 {
70 let location = *location;
71 let diagnostic = CompilerDiagnostic::new(
compiler/crates/react_compiler_validation/src/validate_use_memo.rs
+21 -15
@@ -3,21 +3,26 @@ use std::collections::{HashMap, HashSet};
3 use react_compiler_diagnostics::{
4 CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, SourceLocation,
5 };
6 -use react_compiler_hir::{
7 - FunctionId, HirFunction, IdentifierId, InstructionValue,
8 - ParamPattern, PlaceOrSpread, Place, ReturnVariant, Terminal,
9 -};
6 use react_compiler_hir::environment::Environment;
7 use react_compiler_hir::visitors::{
8 each_instruction_value_operand_with_functions, each_terminal_operand,
9 };
10 +use react_compiler_hir::{
11 + FunctionId, HirFunction, IdentifierId, InstructionValue, ParamPattern, Place, PlaceOrSpread,
12 + ReturnVariant, Terminal,
13 +};
14
15 /// Validates useMemo() usage patterns.
16 ///
17 /// Port of ValidateUseMemo.ts.
18 /// Returns VoidUseMemo errors separately (for logging via logErrors, not as compile errors).
19 pub fn validate_use_memo(func: &HirFunction, env: &mut Environment) -> CompilerError {
20 - validate_use_memo_impl(func, &env.functions, &mut env.errors, env.config.validate_no_void_use_memo)
20 + validate_use_memo_impl(
21 + func,
22 + &env.functions,
23 + &mut env.errors,
24 + env.config.validate_no_void_use_memo,
25 + )
26 }
27
28 /// Information about a FunctionExpression needed for validation.
@@ -36,7 +41,8 @@ fn validate_use_memo_impl(
41 let mut use_memos: HashSet<IdentifierId> = HashSet::new();
42 let mut react: HashSet<IdentifierId> = HashSet::new();
43 let mut func_exprs: HashMap<IdentifierId, FuncExprInfo> = HashMap::new();
39 - let mut unused_use_memos: HashMap<IdentifierId, (SourceLocation, Option<String>)> = HashMap::new();
44 + let mut unused_use_memos: HashMap<IdentifierId, (SourceLocation, Option<String>)> =
45 + HashMap::new();
46
47 for (_block_id, block) in &func.body.blocks {
48 for &instr_id in &block.instructions {
@@ -71,7 +77,9 @@ fn validate_use_memo_impl(
77 }
78 }
79 }
74 - InstructionValue::FunctionExpression { lowered_func, loc, .. } => {
80 + InstructionValue::FunctionExpression {
81 + lowered_func, loc, ..
82 + } => {
83 func_exprs.insert(
84 lvalue.identifier,
85 FuncExprInfo {
@@ -94,9 +102,7 @@ fn validate_use_memo_impl(
102 validate_no_void_use_memo,
103 );
104 }
97 - InstructionValue::MethodCall {
98 - property, args, ..
99 - } => {
105 + InstructionValue::MethodCall { property, args, .. } => {
106 handle_possible_use_memo_call(
107 functions,
108 errors,
@@ -247,10 +253,7 @@ fn handle_possible_use_memo_call(
253 }
254 }
255
250 -fn validate_no_context_variable_assignment(
251 - func: &HirFunction,
252 - errors: &mut CompilerError,
253 -) {
256 +fn validate_no_context_variable_assignment(func: &HirFunction, errors: &mut CompilerError) {
257 let context: HashSet<IdentifierId> =
258 func.context.iter().map(|place| place.identifier).collect();
259
@@ -283,7 +286,10 @@ fn validate_no_context_variable_assignment(
286 fn has_non_void_return(func: &HirFunction) -> bool {
287 for (_block_id, block) in &func.body.blocks {
288 if let Terminal::Return { return_variant, .. } = &block.terminal {
286 - if matches!(return_variant, ReturnVariant::Explicit | ReturnVariant::Implicit) {
289 + if matches!(
290 + return_variant,
291 + ReturnVariant::Explicit | ReturnVariant::Implicit
292 + ) {
293 return true;
294 }
295 }
compiler/scripts/test-e2e.sh
+3 -2
@@ -4,9 +4,10 @@
4 # This source code is licensed under the MIT license found in the
5 # LICENSE file in the root directory of this source tree.
6
7 -# End-to-end test runner for all compiler frontends (Babel, SWC, OXC).
7 +# End-to-end test runner comparing the Rust compiler (Babel/NAPI bridge)
8 +# against the TS reference plugin.
9 #
9 -# Usage: bash compiler/scripts/test-e2e.sh [fixtures-path] [--variant babel|swc|oxc] [--limit N] [--no-color]
10 +# Usage: bash compiler/scripts/test-e2e.sh [fixtures-path] [--variant babel] [--limit N] [--no-color]
11
12 set -eo pipefail
13
compiler/scripts/test-e2e.ts
+31 -166
@@ -6,20 +6,22 @@
6 */
7
8 /**
9 - * End-to-end test script comparing compiler output across all frontends.
9 + * End-to-end test script comparing the Rust compiler against the TS reference.
10 *
11 * Runs fixtures through:
12 - * - TS baseline (Babel plugin, in-process)
13 - * - babel variant: Rust via Babel plugin (in-process via NAPI)
14 - * - swc variant: Rust via SWC frontend (CLI binary)
15 - * - oxc variant: Rust via OXC frontend (CLI binary)
12 + * - TS baseline (Babel plugin, in-process) — the reference output
13 + * - babel variant: Rust via Babel plugin (in-process via NAPI) — the
14 + * production path
15 *
17 - * Usage: npx tsx compiler/scripts/test-e2e.ts [fixtures-path] [--variant babel|swc|oxc] [--limit N] [--no-color]
16 + * This complements `yarn snap --rust` by independently comparing the NAPI
17 + * bridge's code output AND its logged events against the TS plugin.
18 + *
19 + * Usage: npx tsx compiler/scripts/test-e2e.ts [fixtures-path] [--variant babel] [--limit N] [--no-color]
20 */
21
22 import * as babel from '@babel/core';
23 import generate from '@babel/generator';
22 -import {execSync, spawnSync} from 'child_process';
24 +import {execSync} from 'child_process';
25 import fs from 'fs';
26 import path from 'path';
27 import prettier from 'prettier';
@@ -33,7 +35,7 @@ const rawArgs = process.argv.slice(2);
35 const noColor = rawArgs.includes('--no-color') || !!process.env.NO_COLOR;
36 const variantIdx = rawArgs.indexOf('--variant');
37 const variantArg =
36 - variantIdx >= 0 ? (rawArgs[variantIdx + 1] as 'babel' | 'swc' | 'oxc') : null;
38 + variantIdx >= 0 ? (rawArgs[variantIdx + 1] as 'babel') : null;
39 const limitIdx = rawArgs.indexOf('--limit');
40 const limitArg = limitIdx >= 0 ? parseInt(rawArgs[limitIdx + 1], 10) : 50;
41
@@ -97,16 +99,13 @@ function discoverFixtures(rootPath: string): string[] {
99 }
100
101 // --- Build ---
100 -console.log('Building Rust native module and e2e CLI...');
102 +console.log('Building Rust native module...');
103 try {
102 - execSync(
103 - '~/.cargo/bin/cargo build -p react_compiler_napi -p react_compiler_e2e_cli',
104 - {
105 - cwd: path.join(REPO_ROOT, 'compiler/crates'),
106 - stdio: ['inherit', 'pipe', 'pipe'],
107 - shell: true,
108 - },
109 - );
104 + execSync('~/.cargo/bin/cargo build -p react_compiler_napi', {
105 + cwd: path.join(REPO_ROOT, 'compiler/crates'),
106 + stdio: ['inherit', 'pipe', 'pipe'],
107 + shell: true,
108 + });
109 } catch (e: any) {
110 // Show stderr on build failure (includes errors + warnings)
111 if (e.stderr) {
@@ -137,8 +136,6 @@ if (!fs.existsSync(dylib)) {
136 }
137 fs.copyFileSync(dylib, NATIVE_NODE_PATH);
138
140 -const CLI_BINARY = path.join(TARGET_DIR, 'react-compiler-e2e');
141 -
139 // --- Load plugins ---
140 const tsPlugin = require('../packages/babel-plugin-react-compiler/src').default;
141 const rustPlugin =
@@ -224,75 +221,12 @@ function compileBabel(
221 }
222 }
223
227 -// --- Compile via CLI binary ---
228 -function compileCli(
229 - frontend: 'swc' | 'oxc',
230 - fixturePath: string,
231 - source: string,
232 - firstLine: string,
233 -): CompileResult {
234 - const pragmaOpts = parseConfigPragmaForTests(firstLine, {
235 - compilationMode: 'all',
236 - });
237 -
238 - const options = {
239 - shouldCompile: true,
240 - enableReanimated: false,
241 - isDev: false,
242 - ...pragmaOpts,
243 - compilationMode: 'all',
244 - panicThreshold: 'all_errors',
245 - __sourceCode: source,
246 - };
247 -
248 - const result = spawnSync(
249 - CLI_BINARY,
250 - [
251 - '--frontend',
252 - frontend,
253 - '--filename',
254 - fixturePath,
255 - '--options',
256 - JSON.stringify(options),
257 - '--json',
258 - ],
259 - {
260 - input: source,
261 - encoding: 'utf-8',
262 - timeout: 30000,
263 - },
264 - );
265 -
266 - // In JSON mode, the CLI always exits 0 and puts everything in the envelope.
267 - // Non-zero exit means a crash (parse failure, panic), not a compilation error.
268 - if (result.stdout) {
269 - try {
270 - const envelope = JSON.parse(result.stdout);
271 - return {
272 - code: envelope.code ?? null,
273 - error: envelope.error ?? null,
274 - events: envelope.events ?? [],
275 - };
276 - } catch {
277 - // JSON parse failed — fall through to legacy handling
278 - }
279 - }
280 -
281 - // Fallback for crashes or missing stdout
282 - return {
283 - code: null,
284 - error: result.stderr || `Process exited with code ${result.status}`,
285 - events: [],
286 - };
287 -}
288 -
224 // --- Event normalization ---
225 // Strip identifierName (Babel-specific SourceLocation property), sort
291 -// keys for stable comparison, then JSON.stringify.
292 -// SWC uses 1-based columns/indices (from BytePos); adjust to match
293 -// Babel's 0-based convention. OXC is natively 0-based, no adjustment needed.
226 +// keys for stable comparison, then JSON.stringify. Both the TS plugin and
227 +// the Rust NAPI bridge emit 0-based columns/indices, so no positional
228 +// adjustment is needed.
229 const STRIP_KEYS = new Set(['identifierName', 'fnLoc']);
295 -let oneBasedColumns = false;
230
231 function sortAndStrip(obj: unknown): unknown {
232 if (obj === null || typeof obj !== 'object') return obj;
@@ -305,62 +239,6 @@ function sortAndStrip(obj: unknown): unknown {
239 return sorted;
240 }
241
308 -function adjustLoc(loc: Record<string, unknown>): Record<string, unknown> {
309 - const adjusted: Record<string, unknown> = {};
310 - for (const key of Object.keys(loc)) {
311 - const val = loc[key];
312 - if ((key === 'column' || key === 'index') && typeof val === 'number') {
313 - adjusted[key] = val - 1;
314 - } else {
315 - adjusted[key] = val;
316 - }
317 - }
318 - return adjusted;
319 -}
320 -
321 -function adjustDetailLocs(
322 - events: Array<Record<string, unknown>>,
323 -): Array<Record<string, unknown>> {
324 - if (!oneBasedColumns) return events;
325 - return events.map(event => {
326 - if (event.kind !== 'CompileError') return event;
327 - const detail = event.detail as Record<string, unknown> | undefined;
328 - if (!detail) return event;
329 - const newDetail = {...detail};
330 - // Adjust loc on legacy CompilerErrorDetail
331 - if (newDetail.loc && typeof newDetail.loc === 'object') {
332 - const loc = newDetail.loc as Record<string, unknown>;
333 - newDetail.loc = {
334 - start: loc.start
335 - ? adjustLoc(loc.start as Record<string, unknown>)
336 - : loc.start,
337 - end: loc.end ? adjustLoc(loc.end as Record<string, unknown>) : loc.end,
338 - };
339 - }
340 - // Adjust locs inside details array (CompilerDiagnostic)
341 - if (Array.isArray(newDetail.details)) {
342 - newDetail.details = (
343 - newDetail.details as Array<Record<string, unknown>>
344 - ).map(d => {
345 - if (!d.loc || typeof d.loc !== 'object') return d;
346 - const loc = d.loc as Record<string, unknown>;
347 - return {
348 - ...d,
349 - loc: {
350 - start: loc.start
351 - ? adjustLoc(loc.start as Record<string, unknown>)
352 - : loc.start,
353 - end: loc.end
354 - ? adjustLoc(loc.end as Record<string, unknown>)
355 - : loc.end,
356 - },
357 - };
358 - });
359 - }
360 - return {...event, detail: newDetail};
361 - });
362 -}
363 -
242 function stripPipelineErrorStack(
243 events: Array<Record<string, unknown>>,
244 ): Array<Record<string, unknown>> {
@@ -375,11 +253,7 @@ function stripPipelineErrorStack(
253 }
254
255 function normalizeEvents(events: Array<Record<string, unknown>>): string {
378 - return JSON.stringify(
379 - sortAndStrip(adjustDetailLocs(stripPipelineErrorStack(events))),
380 - null,
381 - 2,
382 - );
256 + return JSON.stringify(sortAndStrip(stripPipelineErrorStack(events)), null, 2);
257 }
258
259 // --- Simple unified diff ---
@@ -418,8 +292,8 @@ function unifiedDiff(
292 }
293
294 // --- Main ---
421 -type Variant = 'babel' | 'swc' | 'oxc';
422 -const ALL_VARIANTS: Variant[] = ['babel', 'swc', 'oxc'];
295 +type Variant = 'babel';
296 +const ALL_VARIANTS: Variant[] = ['babel'];
297 const variants: Variant[] = variantArg ? [variantArg] : ALL_VARIANTS;
298
299 const fixtures = discoverFixtures(fixturesPath);
@@ -484,29 +358,20 @@ async function runVariant(
358 for (let i = 0; i < fixtureInfos.length; i++) {
359 const {fixturePath, relPath, source, firstLine, isFlow} = fixtureInfos[i];
360 const tsCode = tsBaselines.get(fixturePath)!;
487 - // All variants emit 0-based columns/indices.
488 - oneBasedColumns = false;
361 const tsEvents = normalizeEvents(tsRawEvents.get(fixturePath)!);
362
363 writeProgress(
492 - ` ${variant}: ${i + 1}/${fixtureInfos.length} (${s.passed} passed, ${s.failed} failed)`,
364 + ` ${variant}: ${i + 1}/${fixtureInfos.length} (${s.passed} passed, ${
365 + s.failed
366 + } failed)`,
367 );
368
495 - // Skip Flow files for SWC/OXC variants — SWC doesn't have a native
496 - // Flow parser, so Flow type cast syntax (e.g., `(x: Type)`) fails.
497 - if (variant !== 'babel' && isFlow) {
498 - s.passed++;
499 - s.codePassed++;
500 - s.eventsPassed++;
501 - continue;
502 - }
503 -
504 - let variantResult: CompileResult;
505 - if (variant === 'babel') {
506 - variantResult = compileBabel(rustPlugin, fixturePath, source, firstLine);
507 - } else {
508 - variantResult = compileCli(variant, fixturePath, source, firstLine);
509 - }
369 + const variantResult = compileBabel(
370 + rustPlugin,
371 + fixturePath,
372 + source,
373 + firstLine,
374 + );
375
376 const variantCode = await formatCode(variantResult.code ?? '', isFlow);
377 const variantEvents = normalizeEvents(variantResult.events);