[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) = ¶ms.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(¶m.pattern);
2633
-
2634
- // Add type annotation if present (now on FormalParameter, not BindingPattern)
2635
- if let Some(_type_annotation) = ¶m.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(¶m.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(¶m.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(¤t)
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
- ¶m.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
- ¶m.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(¶m.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(¶m.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);