@ 261027.7

261018.1 · 4caa87cda978f25211499266fcc99e637c582ebc

chore: update swc to v1.16.18

reading declarations

deps/swc/Cargo.lock

@@ -12,15 +12,6 @@ dependencies = [  "regex", ] -[[package]]-name = "addr2line"-version = "0.24.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"-dependencies = [- "gimli 0.31.1",-]- [[package]] name = "addr2line" version = "0.25.1"@@ -82,12 +73,6 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" -[[package]]-name = "android-tzdata"-version = "0.1.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"- [[package]] name = "android_system_properties" version = "0.1.5"@@ -198,9 +183,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"  [[package]] name = "arrayvec"-version = "0.7.6"+version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"  [[package]] name = "ascii"@@ -294,17 +279,17 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"  [[package]] name = "backtrace"-version = "0.3.74"+version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"+checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [- "addr2line 0.24.1",+ "addr2line",  "cfg-if",  "libc",- "miniz_oxide",- "object 0.36.7",+ "miniz_oxide 0.8.0",+ "object 0.37.3",  "rustc-demangle",- "windows-targets 0.52.6",+ "windows-link", ]  [[package]]@@ -395,7 +380,7 @@ dependencies = [  [[package]] name = "binding_core_wasm"-version = "1.16.2"+version = "1.16.18" dependencies = [  "anyhow",  "getrandom 0.3.4",@@ -408,7 +393,7 @@ dependencies = [  [[package]] name = "binding_es_ast_viewer"-version = "1.16.2"+version = "1.16.18" dependencies = [  "anyhow",  "swc_core",@@ -444,7 +429,7 @@ dependencies = [  [[package]] name = "binding_html_wasm"-version = "1.16.2"+version = "1.16.18" dependencies = [  "anyhow",  "getrandom 0.3.4",@@ -470,7 +455,7 @@ dependencies = [  [[package]] name = "binding_macros"-version = "76.0.0"+version = "80.0.0" dependencies = [  "anyhow",  "console_error_panic_hook",@@ -509,7 +494,7 @@ dependencies = [  [[package]] name = "binding_minifier_wasm"-version = "1.16.2"+version = "1.16.18" dependencies = [  "getrandom 0.3.4",  "serde",@@ -518,9 +503,20 @@ dependencies = [  "wasm-bindgen", ] +[[package]]+name = "binding_native_addon"+version = "0.1.0"+dependencies = [+ "libloading",+ "swc_native_addon",+ "tempfile",+ "tracing",+ "windows-sys 0.52.0",+]+ [[package]] name = "binding_nodejs_support_wasm"-version = "1.16.2"+version = "1.16.18" dependencies = [  "anyhow",  "js-sys",@@ -535,6 +531,7 @@ dependencies = [  "swc_ecma_visit",  "swc_error_reporters",  "swc_ts_fast_strip",+ "testing",  "tracing",  "unicode-width 0.2.2",  "wasm-bindgen",@@ -559,7 +556,7 @@ dependencies = [  [[package]] name = "binding_typescript_wasm"-version = "1.16.2"+version = "1.16.18" dependencies = [  "anyhow",  "js-sys",@@ -626,6 +623,7 @@ dependencies = [  "cc",  "cfg-if",  "constant_time_eq",+ "rayon-core", ]  [[package]]@@ -639,19 +637,20 @@ dependencies = [  [[package]] name = "browserslist-data"-version = "0.2.0"+version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "77d121add86b74ceac9755a74ec3b4abc1f4935711aede468f26a36dc685dc80"+checksum = "58f90c231428ed81dbb8f39dcc14021335c3cc4f46ffdffde278638ca832b579" dependencies = [  "ahash 0.8.12",  "chrono",+ "miniz_oxide 0.9.1", ]  [[package]] name = "browserslist-rs"-version = "0.20.0"+version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "ec54e3c5c50eae1f8d771c7ffc311c1cc8e5e3c85b9fd87b557633bbe9b7cb01"+checksum = "8ca3b73a7c87da0bcca071227be2c28527af6ecd927060a1d75beef8bc4868b8" dependencies = [  "ahash 0.8.12",  "browserslist-data",@@ -757,9 +756,9 @@ dependencies = [  [[package]] name = "bytes-str"-version = "0.2.5"+version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "c02af038c863b51d496dd4a2eabdf781f8f7ab68025e13b984b7f2c525560454"+checksum = "4dde6d05e75a31ec9610eb6446a6f0a10dd30ff5100d720fee4c7c7a9008b5ba" dependencies = [  "bytes",  "serde",@@ -792,7 +791,7 @@ dependencies = [  "cap-primitives",  "cap-std",  "io-lifetimes 2.0.4",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -809,7 +808,7 @@ dependencies = [  "maybe-owned",  "rustix 1.1.2",  "rustix-linux-procfs",- "windows-sys 0.59.0",+ "windows-sys 0.52.0",  "winx", ] @@ -900,6 +899,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [  "find-msvc-tools",+ "jobserver",+ "libc",  "shlex", ] @@ -938,16 +939,15 @@ checksum = "1d001a06f3969c49af3bdf308e9aaa1a477e77d2460357ce9e1248c6a9dcc4c0"  [[package]] name = "chrono"-version = "0.4.40"+version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [- "android-tzdata",  "iana-time-zone",  "js-sys",  "num-traits",  "wasm-bindgen",- "windows-link 0.1.1",+ "windows-link", ]  [[package]]@@ -1795,7 +1795,7 @@ dependencies = [  [[package]] name = "dbg-swc"-version = "62.0.0"+version = "64.0.0" dependencies = [  "anyhow",  "clap 3.2.25",@@ -1982,9 +1982,9 @@ dependencies = [  [[package]] name = "dragonbox_ecma"-version = "0.1.0"+version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "6a5577f010d4e1bb3f3c4d6081e05718eb6992cf20119cab4d3abadff198b5ae"+checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf"  [[package]] name = "dtoa"@@ -2138,7 +2138,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [  "cfg-if",  "rustix 1.1.2",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -2172,7 +2172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [  "crc32fast",- "miniz_oxide",+ "miniz_oxide 0.8.0", ]  [[package]]@@ -2233,7 +2233,7 @@ checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [  "io-lifetimes 2.0.4",  "rustix 1.1.2",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -2392,11 +2392,22 @@ dependencies = [  "cfg-if",  "js-sys",  "libc",- "r-efi",+ "r-efi 5.2.0",  "wasip2",  "wasm-bindgen", ] +[[package]]+name = "getrandom"+version = "0.4.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"+dependencies = [+ "cfg-if",+ "libc",+ "r-efi 6.0.0",+]+ [[package]] name = "gimli" version = "0.28.1"@@ -2408,12 +2419,6 @@ dependencies = [  "stable_deref_trait", ] -[[package]]-name = "gimli"-version = "0.31.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"- [[package]] name = "gimli" version = "0.32.3"@@ -2427,15 +2432,15 @@ dependencies = [  [[package]] name = "glob"-version = "0.3.2"+version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"+checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"  [[package]] name = "globset"-version = "0.4.16"+version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"+checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" dependencies = [  "aho-corasick",  "bstr",@@ -2613,7 +2618,7 @@ checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f"  [[package]] name = "hstr"-version = "4.0.1"+version = "4.0.2" dependencies = [  "compact_str",  "criterion",@@ -2783,18 +2788,6 @@ dependencies = [  "cc", ] -[[package]]-name = "icu_collections"-version = "1.5.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"-dependencies = [- "displaydoc",- "yoke 0.7.5",- "zerofrom",- "zerovec 0.10.4",-]- [[package]] name = "icu_collections" version = "2.1.1"@@ -2804,9 +2797,9 @@ dependencies = [  "displaydoc",  "potential_utf",  "serde",- "yoke 0.8.1",+ "yoke",  "zerofrom",- "zerovec 0.11.5",+ "zerovec", ]  [[package]]@@ -2816,83 +2809,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [  "displaydoc",- "litemap 0.8.1",- "tinystr 0.8.2",- "writeable 0.6.2",- "zerovec 0.11.5",-]--[[package]]-name = "icu_locid"-version = "1.5.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"-dependencies = [- "displaydoc",- "litemap 0.7.5",- "tinystr 0.7.6",- "writeable 0.5.5",- "zerovec 0.10.4",-]--[[package]]-name = "icu_locid_transform"-version = "1.5.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"-dependencies = [- "displaydoc",- "icu_locid",- "icu_locid_transform_data",- "icu_provider 1.5.0",- "tinystr 0.7.6",- "zerovec 0.10.4",+ "litemap",+ "tinystr",+ "writeable",+ "zerovec", ] -[[package]]-name = "icu_locid_transform_data"-version = "1.5.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"- [[package]] name = "icu_normalizer"-version = "1.5.0"+version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [- "displaydoc",- "icu_collections 1.5.0",+ "icu_collections",  "icu_normalizer_data",- "icu_properties 1.5.1",- "icu_provider 1.5.0",+ "icu_properties",+ "icu_provider",  "smallvec",- "utf16_iter",- "utf8_iter",- "write16",- "zerovec 0.10.4",+ "zerovec", ]  [[package]] name = "icu_normalizer_data"-version = "1.5.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"--[[package]]-name = "icu_properties"-version = "1.5.1"+version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"-dependencies = [- "displaydoc",- "icu_collections 1.5.0",- "icu_locid_transform",- "icu_properties_data 1.5.1",- "icu_provider 1.5.0",- "tinystr 0.7.6",- "zerovec 0.10.4",-]+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"  [[package]] name = "icu_properties"@@ -2900,44 +2841,21 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [- "icu_collections 2.1.1",+ "icu_collections",  "icu_locale_core",- "icu_properties_data 2.1.2",- "icu_provider 2.1.1",+ "icu_properties_data",+ "icu_provider",  "serde",  "zerotrie",- "zerovec 0.11.5",+ "zerovec", ] -[[package]]-name = "icu_properties_data"-version = "1.5.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"- [[package]] name = "icu_properties_data" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" -[[package]]-name = "icu_provider"-version = "1.5.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"-dependencies = [- "displaydoc",- "icu_locid",- "icu_provider_macros",- "stable_deref_trait",- "tinystr 0.7.6",- "writeable 0.5.5",- "yoke 0.7.5",- "zerofrom",- "zerovec 0.10.4",-]- [[package]] name = "icu_provider" version = "2.1.1"@@ -2946,22 +2864,11 @@ checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [  "displaydoc",  "icu_locale_core",- "writeable 0.6.2",- "yoke 0.8.1",+ "writeable",+ "yoke",  "zerofrom",  "zerotrie",- "zerovec 0.11.5",-]--[[package]]-name = "icu_provider_macros"-version = "1.5.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"-dependencies = [- "proc-macro2",- "quote",- "syn 2.0.110",+ "zerovec", ]  [[package]]@@ -2989,12 +2896,12 @@ dependencies = [  [[package]] name = "idna_adapter"-version = "1.2.0"+version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [  "icu_normalizer",- "icu_properties 1.5.1",+ "icu_properties", ]  [[package]]@@ -3083,7 +2990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [  "io-lifetimes 2.0.4",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -3120,9 +3027,9 @@ dependencies = [  [[package]] name = "is-macro"-version = "0.3.7"+version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"+checksum = "8267aa6001e25494f3015f9663bbd88a18240c74483afa5f0934a1b3e4c388e9" dependencies = [  "heck 0.5.0",  "proc-macro2",@@ -3192,6 +3099,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]]+name = "jobserver"+version = "0.1.35"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"+dependencies = [+ "getrandom 0.4.3",+ "libc",+]+ [[package]] name = "js-sys" version = "0.3.77"@@ -3356,18 +3273,18 @@ dependencies = [  [[package]] name = "libc"-version = "0.2.177"+version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"  [[package]] name = "libloading"-version = "0.8.1"+version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [  "cfg-if",- "windows-sys 0.48.0",+ "windows-link", ]  [[package]]@@ -3378,12 +3295,11 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"  [[package]] name = "libmimalloc-sys"-version = "0.1.40"+version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502"+checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [  "cc",- "libc", ]  [[package]]@@ -3486,12 +3402,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" -[[package]]-name = "litemap"-version = "0.7.5"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"- [[package]] name = "litemap" version = "0.8.1"@@ -3657,9 +3567,9 @@ dependencies = [  [[package]] name = "mimalloc"-version = "0.1.44"+version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1"+checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [  "libmimalloc-sys", ]@@ -3685,6 +3595,15 @@ dependencies = [  "adler2", ] +[[package]]+name = "miniz_oxide"+version = "0.9.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"+dependencies = [+ "adler2",+]+ [[package]] name = "mio" version = "1.0.2"@@ -4398,7 +4317,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [- "zerovec 0.11.5",+ "zerovec", ]  [[package]]@@ -4457,7 +4376,7 @@ dependencies = [  [[package]] name = "preset_env_base"-version = "9.0.0"+version = "10.0.0" dependencies = [  "anyhow",  "browserslist-rs",@@ -4679,6 +4598,12 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]]+name = "r-efi"+version = "6.0.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"+ [[package]] name = "radium" version = "0.7.0"@@ -4770,9 +4695,9 @@ dependencies = [  [[package]] name = "rayon"-version = "1.11.0"+version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [  "either",  "rayon-core",@@ -4780,9 +4705,9 @@ dependencies = [  [[package]] name = "rayon-core"-version = "1.13.0"+version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [  "crossbeam-deque",  "crossbeam-utils",@@ -5002,9 +4927,9 @@ dependencies = [  [[package]] name = "regex-automata"-version = "0.4.7"+version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [  "aho-corasick",  "memchr",@@ -5013,9 +4938,9 @@ dependencies = [  [[package]] name = "regex-syntax"-version = "0.8.4"+version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"  [[package]] name = "region"@@ -5226,7 +5151,7 @@ dependencies = [  "errno",  "libc",  "linux-raw-sys 0.4.15",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -5607,9 +5532,9 @@ dependencies = [  [[package]] name = "sha2"-version = "0.10.8"+version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [  "cfg-if",  "cpufeatures",@@ -5772,15 +5697,15 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"  [[package]] name = "stacker"-version = "0.1.20"+version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9"+checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" dependencies = [  "cc",  "cfg-if",  "libc",  "psm",- "windows-sys 0.59.0",+ "windows-sys 0.60.2", ]  [[package]]@@ -5854,7 +5779,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"  [[package]] name = "swc"-version = "76.0.0"+version = "80.0.0" dependencies = [  "ansi_term",  "anyhow",@@ -5978,7 +5903,7 @@ dependencies = [  [[package]] name = "swc_bundler"-version = "56.0.1"+version = "57.0.0" dependencies = [  "anyhow",  "crc",@@ -6020,7 +5945,7 @@ dependencies = [  [[package]] name = "swc_cli"-version = "0.123.2"+version = "0.127.7" dependencies = [  "anyhow",  "par-core",@@ -6029,11 +5954,12 @@ dependencies = [  [[package]] name = "swc_cli_impl"-version = "78.0.0"+version = "82.0.0" dependencies = [  "anyhow",  "assert_cmd",  "assert_fs",+ "base64 0.22.1",  "clap 3.2.25",  "glob",  "notify",@@ -6080,7 +6006,7 @@ dependencies = [  [[package]] name = "swc_compiler_base"-version = "64.0.0"+version = "66.0.0" dependencies = [  "anyhow",  "base64 0.22.1",@@ -6105,7 +6031,7 @@ dependencies = [  [[package]] name = "swc_config"-version = "6.0.0"+version = "6.0.1" dependencies = [  "anyhow",  "bytes-str",@@ -6134,7 +6060,7 @@ dependencies = [  [[package]] name = "swc_core"-version = "78.0.0"+version = "82.0.2" dependencies = [  "anyhow",  "binding_macros",@@ -6187,7 +6113,7 @@ dependencies = [  [[package]] name = "swc_css"-version = "31.0.0"+version = "32.0.0" dependencies = [  "swc_css_ast",  "swc_css_codegen",@@ -6326,7 +6252,7 @@ dependencies = [  [[package]] name = "swc_css_prefixer"-version = "31.0.0"+version = "32.0.0" dependencies = [  "once_cell",  "preset_env_base",@@ -6369,7 +6295,7 @@ dependencies = [  [[package]] name = "swc_ecma_ast"-version = "29.0.0"+version = "29.0.2" dependencies = [  "arbitrary",  "bitflags 2.10.0",@@ -6391,7 +6317,7 @@ dependencies = [  [[package]] name = "swc_ecma_codegen"-version = "32.0.0"+version = "32.0.3" dependencies = [  "ascii",  "base64 0.22.1",@@ -6430,7 +6356,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_bugfixes"-version = "56.0.0"+version = "57.0.0" dependencies = [  "rustc-hash 2.1.1",  "swc_atoms",@@ -6447,7 +6373,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_common"-version = "45.0.0"+version = "46.0.0" dependencies = [  "swc_common",  "swc_ecma_ast",@@ -6457,7 +6383,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2015"-version = "55.0.0"+version = "56.0.0" dependencies = [  "arrayvec",  "indexmap 2.12.0",@@ -6484,7 +6410,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2016"-version = "50.0.0"+version = "51.0.0" dependencies = [  "swc_ecma_ast",  "swc_ecma_parser",@@ -6497,7 +6423,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2017"-version = "50.0.0"+version = "51.0.0" dependencies = [  "serde",  "swc_common",@@ -6510,7 +6436,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2018"-version = "51.0.0"+version = "52.0.0" dependencies = [  "serde",  "swc_ecma_ast",@@ -6522,7 +6448,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2019"-version = "50.0.0"+version = "51.0.0" dependencies = [  "swc_common",  "swc_ecma_ast",@@ -6536,7 +6462,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2020"-version = "53.0.0"+version = "54.0.0" dependencies = [  "serde",  "swc_common",@@ -6551,7 +6477,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2021"-version = "50.0.0"+version = "51.0.0" dependencies = [  "swc_ecma_ast",  "swc_ecma_transformer",@@ -6562,7 +6488,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es2022"-version = "53.0.0"+version = "54.0.0" dependencies = [  "rustc-hash 2.1.1",  "swc_atoms",@@ -6579,7 +6505,7 @@ dependencies = [  [[package]] name = "swc_ecma_compat_es3"-version = "41.0.0"+version = "42.0.0" dependencies = [  "swc_ecma_ast",  "swc_ecma_parser",@@ -6591,7 +6517,7 @@ dependencies = [ name = "swc_ecma_compat_regexp" version = "9.0.0" dependencies = [- "icu_properties 2.1.2",+ "icu_properties",  "swc_ecma_regexp",  "swc_ecma_regexp_ast",  "swc_ecma_regexp_visit",@@ -6620,7 +6546,7 @@ dependencies = [  [[package]] name = "swc_ecma_lexer"-version = "44.0.0"+version = "45.0.0" dependencies = [  "bitflags 2.10.0",  "compact_str",@@ -6687,7 +6613,7 @@ dependencies = [  [[package]] name = "swc_ecma_minifier"-version = "61.0.5"+version = "63.0.1" dependencies = [  "ansi_term",  "anyhow",@@ -6730,7 +6656,7 @@ dependencies = [  [[package]] name = "swc_ecma_parser"-version = "45.1.1"+version = "46.0.0" dependencies = [  "bitflags 2.10.0",  "cbor4ii",@@ -6758,7 +6684,7 @@ dependencies = [  [[package]] name = "swc_ecma_preset_env"-version = "63.0.0"+version = "66.0.0" dependencies = [  "anyhow",  "codspeed-criterion-compat",@@ -6779,6 +6705,8 @@ dependencies = [  "swc_ecma_parser",  "swc_ecma_transformer",  "swc_ecma_transforms",+ "swc_ecma_transforms_base",+ "swc_ecma_transforms_testing",  "swc_ecma_utils",  "swc_ecma_visit",  "testing",@@ -6786,7 +6714,7 @@ dependencies = [  [[package]] name = "swc_ecma_quote"-version = "45.0.0"+version = "46.0.0" dependencies = [  "swc_common",  "swc_ecma_ast",@@ -6795,7 +6723,7 @@ dependencies = [  [[package]] name = "swc_ecma_quote_macros"-version = "45.0.1"+version = "46.0.0" dependencies = [  "anyhow",  "proc-macro2",@@ -6811,7 +6739,7 @@ dependencies = [  [[package]] name = "swc_ecma_react_compiler"-version = "25.0.0"+version = "26.0.0" dependencies = [  "indexmap 2.12.0",  "react_compiler",@@ -6825,6 +6753,7 @@ dependencies = [  "swc_ecma_ast",  "swc_ecma_codegen",  "swc_ecma_parser",+ "swc_ecma_utils",  "swc_ecma_visit",  "testing", ]@@ -6874,7 +6803,7 @@ dependencies = [  [[package]] name = "swc_ecma_testing"-version = "27.0.0"+version = "27.0.1" dependencies = [  "anyhow",  "hex",@@ -6885,7 +6814,7 @@ dependencies = [  [[package]] name = "swc_ecma_transformer"-version = "21.0.1"+version = "22.0.0" dependencies = [  "rustc-hash 2.1.1",  "swc_atoms",@@ -6902,7 +6831,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms"-version = "62.0.0"+version = "64.0.0" dependencies = [  "par-core",  "swc_common",@@ -6922,7 +6851,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_base"-version = "49.0.1"+version = "50.0.0" dependencies = [  "better_scoped_tls",  "codspeed-criterion-compat",@@ -6948,7 +6877,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_classes"-version = "49.0.0"+version = "50.0.0" dependencies = [  "swc_common",  "swc_ecma_ast",@@ -6959,7 +6888,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_compat"-version = "57.0.1"+version = "58.0.0" dependencies = [  "indexmap 2.12.0",  "par-core",@@ -7000,7 +6929,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_module"-version = "55.0.1"+version = "57.0.0" dependencies = [  "Inflector",  "anyhow",@@ -7032,7 +6961,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_optimization"-version = "52.0.0"+version = "53.0.0" dependencies = [  "bytes-str",  "dashmap 6.1.0",@@ -7061,7 +6990,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_proposal"-version = "49.0.1"+version = "50.0.0" dependencies = [  "either",  "rustc-hash 2.1.1",@@ -7083,7 +7012,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_react"-version = "55.0.1"+version = "56.0.0" dependencies = [  "base64 0.22.1",  "bytes-str",@@ -7110,7 +7039,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_testing"-version = "53.0.0"+version = "54.0.0" dependencies = [  "ansi_term",  "anyhow",@@ -7134,7 +7063,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_typescript"-version = "55.0.2"+version = "56.0.0" dependencies = [  "bytes-str",  "codspeed-criterion-compat",@@ -7158,7 +7087,7 @@ dependencies = [  [[package]] name = "swc_ecma_utils"-version = "35.0.1"+version = "35.0.3" dependencies = [  "dragonbox_ecma",  "indexmap 2.12.0",@@ -7191,7 +7120,7 @@ dependencies = [  [[package]] name = "swc_ecmascript"-version = "70.1.0"+version = "74.0.0" dependencies = [  "par-core",  "swc_ecma_ast",@@ -7227,7 +7156,7 @@ dependencies = [  [[package]] name = "swc_estree_ast"-version = "26.0.0"+version = "27.0.0" dependencies = [  "better_scoped_tls",  "serde",@@ -7238,7 +7167,7 @@ dependencies = [  [[package]] name = "swc_estree_compat"-version = "48.0.0"+version = "49.0.0" dependencies = [  "anyhow",  "codspeed-criterion-compat",@@ -7320,7 +7249,7 @@ dependencies = [  [[package]] name = "swc_html_minifier"-version = "61.0.0"+version = "63.0.0" dependencies = [  "codspeed-criterion-compat",  "once_cell",@@ -7351,7 +7280,7 @@ dependencies = [  [[package]] name = "swc_html_parser"-version = "26.0.0"+version = "26.1.0" dependencies = [  "codspeed-criterion-compat",  "rustc-hash 2.1.1",@@ -7405,9 +7334,38 @@ dependencies = [  "tikv-jemallocator", ] +[[package]]+name = "swc_native_addon"+version = "0.1.0"+dependencies = [+ "blake3",+ "jobserver",+ "libc",+ "libloading",+ "rayon-core",+ "sha2",+ "tempfile",+ "tracing",+ "windows-sys 0.52.0",+ "xattr",+ "zstd",+]++[[package]]+name = "swc_native_addon_pack"+version = "0.1.0"+dependencies = [+ "memchr",+ "object 0.36.7",+ "serde_json",+ "sha2",+ "swc_native_addon",+ "tempfile",+]+ [[package]] name = "swc_node_bundler"-version = "77.0.0"+version = "81.0.0" dependencies = [  "anyhow",  "rustc-hash 2.1.1",@@ -7578,7 +7536,7 @@ dependencies = [  [[package]] name = "swc_ts_fast_strip"-version = "58.0.0"+version = "59.0.0" dependencies = [  "anyhow",  "bytes-str",@@ -7599,7 +7557,7 @@ dependencies = [  [[package]] name = "swc_ts_fast_strip_binding"-version = "0.23.0"+version = "0.24.0" dependencies = [  "anyhow",  "codspeed-criterion-compat",@@ -7617,7 +7575,7 @@ dependencies = [  [[package]] name = "swc_typescript"-version = "34.0.0"+version = "34.0.1" dependencies = [  "bitflags 2.10.0",  "petgraph",@@ -7785,7 +7743,7 @@ dependencies = [  "fd-lock",  "io-lifetimes 2.0.4",  "rustix 0.38.44",- "windows-sys 0.59.0",+ "windows-sys 0.52.0",  "winx", ] @@ -8021,16 +7979,6 @@ dependencies = [  "time-core", ] -[[package]]-name = "tinystr"-version = "0.7.6"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"-dependencies = [- "displaydoc",- "zerovec 0.10.4",-]- [[package]] name = "tinystr" version = "0.8.2"@@ -8038,7 +7986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [  "displaydoc",- "zerovec 0.11.5",+ "zerovec", ]  [[package]]@@ -8231,9 +8179,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"  [[package]] name = "tracing"-version = "0.1.41"+version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [  "pin-project-lite",  "tracing-attributes",@@ -8242,9 +8190,9 @@ dependencies = [  [[package]] name = "tracing-attributes"-version = "0.1.28"+version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [  "proc-macro2",  "quote",@@ -8253,9 +8201,9 @@ dependencies = [  [[package]] name = "tracing-core"-version = "0.1.33"+version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [  "once_cell",  "valuable",@@ -8418,12 +8366,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]]-name = "utf16_iter"-version = "1.0.5"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"- [[package]] name = "utf8_iter" version = "1.0.4"@@ -9160,7 +9102,7 @@ version = "38.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81eafc07c867be94c47e0dc66355d9785e09107a18901f76a20701ba0663ad7" dependencies = [- "addr2line 0.25.1",+ "addr2line",  "anyhow",  "async-trait",  "bitflags 2.10.0",@@ -9526,7 +9468,7 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [  "windows-implement",  "windows-interface",- "windows-link 0.2.1",+ "windows-link",  "windows-result",  "windows-strings", ]@@ -9553,12 +9495,6 @@ dependencies = [  "syn 2.0.110", ] -[[package]]-name = "windows-link"-version = "0.1.1"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"- [[package]] name = "windows-link" version = "0.2.1"@@ -9571,7 +9507,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [- "windows-link 0.2.1",+ "windows-link",  "windows-result",  "windows-strings", ]@@ -9582,7 +9518,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [- "windows-link 0.2.1",+ "windows-link", ]  [[package]]@@ -9591,7 +9527,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [- "windows-link 0.2.1",+ "windows-link", ]  [[package]]@@ -9636,7 +9572,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [- "windows-link 0.2.1",+ "windows-link", ]  [[package]]@@ -9676,7 +9612,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [- "windows-link 0.2.1",+ "windows-link",  "windows_aarch64_gnullvm 0.53.1",  "windows_aarch64_msvc 0.53.1",  "windows_i686_gnu 0.53.1",@@ -9850,7 +9786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [  "bitflags 2.10.0",- "windows-sys 0.59.0",+ "windows-sys 0.52.0", ]  [[package]]@@ -9871,18 +9807,6 @@ dependencies = [  "wast 35.0.2", ] -[[package]]-name = "write16"-version = "1.0.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"--[[package]]-name = "writeable"-version = "0.5.5"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"- [[package]] name = "writeable" version = "0.6.2"@@ -9936,18 +9860,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" -[[package]]-name = "yoke"-version = "0.7.5"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"-dependencies = [- "serde",- "stable_deref_trait",- "yoke-derive 0.7.5",- "zerofrom",-]- [[package]] name = "yoke" version = "0.8.1"@@ -9955,22 +9867,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [  "stable_deref_trait",- "yoke-derive 0.8.1",+ "yoke-derive",  "zerofrom", ] -[[package]]-name = "yoke-derive"-version = "0.7.5"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"-dependencies = [- "proc-macro2",- "quote",- "syn 2.0.110",- "synstructure",-]- [[package]] name = "yoke-derive" version = "0.8.1"@@ -10058,21 +9958,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [  "displaydoc",- "yoke 0.8.1",+ "yoke",  "zerofrom", ] -[[package]]-name = "zerovec"-version = "0.10.4"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"-dependencies = [- "yoke 0.7.5",- "zerofrom",- "zerovec-derive 0.10.3",-]- [[package]] name = "zerovec" version = "0.11.5"@@ -10080,16 +9969,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [  "serde",- "yoke 0.8.1",+ "yoke",  "zerofrom",- "zerovec-derive 0.11.2",+ "zerovec-derive", ]  [[package]] name = "zerovec-derive"-version = "0.10.3"+version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [  "proc-macro2",  "quote",@@ -10097,12 +9986,29 @@ dependencies = [ ]  [[package]]-name = "zerovec-derive"-version = "0.11.2"+name = "zstd"+version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [- "proc-macro2",- "quote",- "syn 2.0.110",+ "zstd-safe",+]++[[package]]+name = "zstd-safe"+version = "7.3.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"+dependencies = [+ "zstd-sys",+]++[[package]]+name = "zstd-sys"+version = "2.1.0+zstd.1.5.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"+dependencies = [+ "cc",+ "pkg-config", ]

deps/swc/Cargo.toml

@@ -5,6 +5,7 @@ members = [   "crates/*",   "tools/generate-code",   "tools/swc-releaser",+  "tools/swc-native-addon-pack", ] resolver = "2" @@ -29,7 +30,7 @@ backtrace                 = "0.3" base64                    = "0.22.1" bitflags                  = "2.5.0" blake3                    = "1.5.4"-browserslist-rs           = "0.20.0"+browserslist-rs           = { version = "0.21.2", features = ["deflate"] } bumpalo                   = "3.16.0" bytes-str                 = "0.2.5" cargo_metadata            = "0.18.1"

deps/swc/bindings/binding_core_node/src/parse.rs

@@ -234,7 +234,7 @@ pub fn parse_file_sync(path: String, opts: Buffer) -> napi::Result<String> {         try_with(c.cm.clone(), false, ErrorFormat::Normal, |handler| {             let fm =                 c.cm.load_file(Path::new(path.as_str()))-                    .expect("failed to read program file");+                    .context("failed to read program file")?;              let local_comments = SwcComments::default();             let comments = if options.comments {

deps/swc/bindings/binding_core_node/src/transform.rs

@@ -267,7 +267,9 @@ pub fn transform_file_sync(             error_format,             |handler| {                 c.run(|| {-                    let fm = c.cm.load_file(Path::new(&s)).expect("failed to load file");+                    let fm =+                        c.cm.load_file(Path::new(&s))+                            .context("failed to load file")?;                     c.process_js_file(fm, handler, &options)                 })             },

deps/swc/bindings/binding_core_wasm/Cargo.toml

@@ -6,7 +6,7 @@ license     = { workspace = true } name        = "binding_core_wasm" publish     = false repository  = { workspace = true }-version     = "1.16.2"+version     = "1.16.18"  [lib] bench      = false

deps/swc/bindings/binding_core_wasm/example/package.json

@@ -6,7 +6,7 @@     },     "devDependencies": {         "copyfiles": "^2.3.0",-        "npm-run-all": "^4.1.5",+        "npm-run-all2": "^5.0.0",         "webpack": "^4.25.1",         "webpack-cli": "^3.1.2",         "webpack-dev-server": "^3.1.10"

deps/swc/bindings/binding_core_wasm/package.json

@@ -1,5 +1,5 @@ {     "devDependencies": {-        "@rstest/core": "0.7.8"+        "@rstest/core": "0.7.9"     } }

deps/swc/bindings/binding_core_wasm/src/types.rs

@@ -1293,6 +1293,7 @@ export interface UmdConfig extends BaseModuleConfig { export interface AmdConfig extends BaseModuleConfig {   type: "amd";   moduleId?: string;+  moduleRoot?: string; } export interface SystemjsConfig {   type: "systemjs";

deps/swc/bindings/binding_es_ast_viewer/Cargo.toml

@@ -5,7 +5,7 @@ license    = { workspace = true } name       = "binding_es_ast_viewer" publish    = false repository = { workspace = true }-version    = "1.16.2"+version    = "1.16.18"  [dependencies] anyhow       = { workspace = true }

deps/swc/bindings/binding_html_wasm/Cargo.toml

@@ -6,7 +6,7 @@ license     = { workspace = true } name        = "binding_html_wasm" publish     = false repository  = { workspace = true }-version     = "1.16.2"+version     = "1.16.18"  [lib] bench      = false

deps/swc/bindings/binding_minifier_wasm/Cargo.toml

@@ -6,7 +6,7 @@ license     = { workspace = true } name        = "binding_minifier_wasm" publish     = false repository  = { workspace = true }-version     = "1.16.2"+version     = "1.16.18"  [lib] bench      = false

deps/swc/bindings/binding_minifier_wasm/src/types.rs

@@ -1190,6 +1190,7 @@ export interface UmdConfig extends BaseModuleConfig { export interface AmdConfig extends BaseModuleConfig {   type: "amd";   moduleId?: string;+  moduleRoot?: string; } export interface SystemjsConfig {   type: "systemjs";

deps/swc/bindings/binding_native_addon/Cargo.toml

new file mode 100644@@ -0,0 +1,36 @@+[package]+name = "binding_native_addon"+version = "0.1.0"+edition = "2021"+rust-version = "1.85"+license = "Apache-2.0"+publish = false+build = "build.rs"++[lib]+crate-type = ["cdylib", "rlib"]++[features]+embedded-payload = []++[dependencies]+swc_native_addon = { path = "../../crates/swc_native_addon" }+libloading = "=0.8.9"+tracing = { version = "=0.1.44", default-features = false, features = ["std"] }++[build-dependencies]+swc_native_addon = { path = "../../crates/swc_native_addon" }+tempfile = "=3.12.0"++[dev-dependencies]+tempfile = "=3.12.0"++[package.metadata.cargo-shear]+# The carrier imports the shared ACL fixture with #[path] from swc_native_addon.+# cargo-shear does not scan that external source, which uses windows-sys.+ignored = ["windows-sys"]++[target.'cfg(windows)'.dev-dependencies]+windows-sys = { version = "=0.52.0", features = [+  "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization",+] }

deps/swc/bindings/binding_native_addon/NOTICE

new file mode 100644@@ -0,0 +1,24 @@+The registration descriptor and constructor contract in src/legacy.rs follow+the public N-API NAPI_MODULE_X interface in Node.js v10.0.0's src/node_api.h.+The following Node.js attribution is retained for that interface adaptation.+No implementation from SWC PR #12000 or the napi-rs compression PoC is used.++Copyright Node.js contributors. All rights reserved.++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to+deal in the Software without restriction, including without limitation the+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or+sell copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS+IN THE SOFTWARE.

deps/swc/bindings/binding_native_addon/build.rs

new file mode 100644@@ -0,0 +1,81 @@+use std::{env, fs, path::PathBuf};++use swc_native_addon::{+    format::{NativeTarget, Payload},+    integrity::RuntimeIntegrity,+};++fn main() {+    println!("cargo:rerun-if-env-changed=SWC_NATIVE_BINDING_PAYLOAD");+    let output = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("payload.rs");+    if env::var_os("CARGO_FEATURE_EMBEDDED_PAYLOAD").is_none() {+        fs::write(+            output,+            "static PAYLOAD: &[u8] = &[];\nstatic INTEGRITY: &[u8] = &[];\n",+        )+        .unwrap();+        return;+    }+    let target = env::var("TARGET").unwrap();+    let target = NativeTarget::parse(&target).expect("native carrier target");+    let path = PathBuf::from(+        env::var_os("SWC_NATIVE_BINDING_PAYLOAD")+            .expect("embedded-payload requires SWC_NATIVE_BINDING_PAYLOAD=/absolute/payload.swcn"),+    );+    assert!(+        path.is_absolute(),+        "SWC_NATIVE_BINDING_PAYLOAD must be absolute"+    );+    println!("cargo:rerun-if-changed={}", path.display());+    let bytes = fs::read(&path).expect("read native addon payload");+    let payload = Payload::parse(&bytes).expect("validate native addon payload header");+    assert_eq!(+        payload.header.target, target,+        "embedded native addon payload target does not match Cargo TARGET"+    );+    let output_parent = output.parent().expect("OUT_DIR has a parent");+    let mut raw = tempfile::Builder::new()+        .prefix(".swc-native-addon-validate-")+        .tempfile_in(output_parent)+        .expect("stage decoded native addon validation");+    payload+        .decode_into(&mut raw)+        .expect("decode embedded native addon bytes");+    payload+        .verify_target(&mut raw)+        .expect("validate embedded native addon target");+    let integrity = RuntimeIntegrity::from_raw(&payload.header, &mut raw)+        .expect("derive runtime integrity from SHA-512 verified raw addon");+    let integrity_path = output.with_file_name("integrity.swcn");+    fs::write(&integrity_path, integrity.encode()).expect("snapshot native runtime integrity");+    // Copy to OUT_DIR so a later input replacement cannot change the validated+    // payload between build-script validation and rustc's include_bytes call.+    let embedded = output.with_file_name("payload.swcn");+    fs::write(&embedded, &bytes).expect("snapshot validated addon payload");+    // Retain a complete, mapped payload for final-artifact verification. LTO+    // may otherwise fold header reads and eliminate parts of include_bytes.+    // Section names fit the Mach-O (16-byte) and PE (8-byte) limits.+    fs::write(+        output,+        format!(+            r#"#[used]+#[cfg_attr(target_os = "linux", link_section = ".swc_native")]+#[cfg_attr(target_os = "macos", link_section = "__TEXT,__swc_native")]+#[cfg_attr(windows, link_section = ".swcn")]+static EMBEDDED_PAYLOAD: [u8; {}] = *include_bytes!({:?});+static PAYLOAD: &[u8] = &EMBEDDED_PAYLOAD;+#[used]+#[cfg_attr(target_os = "linux", link_section = ".swc_integrity")]+#[cfg_attr(target_os = "macos", link_section = "__TEXT,__swc_integrity")]+#[cfg_attr(windows, link_section = ".swci")]+static EMBEDDED_INTEGRITY: [u8; {}] = *include_bytes!({:?});+static INTEGRITY: &[u8] = &EMBEDDED_INTEGRITY;+"#,+            payload.header.compressed_len + swc_native_addon::format::HEADER_LEN as u64,+            embedded,+            swc_native_addon::integrity::INTEGRITY_LEN,+            integrity_path,+        ),+    )+    .unwrap();+}

deps/swc/bindings/binding_native_addon/src/legacy.rs

new file mode 100644@@ -0,0 +1,58 @@+//! Node 10.0 predates lookup of napi_register_module_v1 by symbol. Its original+//! napi_module_register entry point is required even though all APIs used by+//! the initializer itself exist there. New Node versions also accept this hook.+//!+//! Constructors only register metadata. Decompression, IO, and native loading+//! stay in the initializer, outside the Windows loader lock.+//! See ../NOTICE for the Node.js MIT attribution of this public ABI adaptation.++use std::{+    ffi::{c_char, c_void},+    ptr,+};++use crate::napi::Register;++#[repr(C)]+struct Module {+    version: i32,+    flags: u32,+    filename: *const c_char,+    register: Register,+    name: *const c_char,+    private: *mut c_void,+    reserved: [*mut c_void; 4],+}++static mut MODULE: Module = Module {+    version: 1,+    flags: 0,+    filename: b"swc-native-carrier\0".as_ptr().cast(),+    register: crate::napi_register_module_v1,+    name: b"swc_native_carrier\0".as_ptr().cast(),+    private: ptr::null_mut(),+    reserved: [ptr::null_mut(); 4],+};++unsafe extern "C" fn register() {+    #[cfg(unix)]+    let process = libloading::os::unix::Library::this();+    #[cfg(windows)]+    let process = match libloading::os::windows::Library::this() {+        Ok(process) => process,+        Err(_) => return,+    };+    // Test executables are not Node hosts. They can link the rlib without+    // registering anything; Node always exports this original N-API function.+    if let Ok(register) =+        process.get::<unsafe extern "C" fn(*mut Module)>(b"napi_module_register\0")+    {+        register(ptr::addr_of_mut!(MODULE));+    }+}++#[used]+#[cfg_attr(target_os = "linux", link_section = ".init_array")]+#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]+#[cfg_attr(windows, link_section = ".CRT$XCU")]+static INITIALIZER: unsafe extern "C" fn() = register;

deps/swc/bindings/binding_native_addon/src/lib.rs

new file mode 100644@@ -0,0 +1,191 @@+//! Self-loading target carrier. All native image bytes are verified before the+//! OS loader sees them; N-API registration is forwarded without wrapping+//! exports.++mod legacy;+mod napi;++use std::sync::Mutex;++use libloading::Library;+use swc_native_addon::{+    cache::{self, CacheMode, Materialized},+    format::Payload,+    integrity::RuntimeIntegrity,+    platform, replacement, Error, ErrorKind, Result,+};++include!(concat!(env!("OUT_DIR"), "/payload.rs"));++struct Loaded {+    register: napi::Register,+    _library: Library,+    _materialized: Materialized,+}++// Statics are deliberately never dropped. Addon callbacks can survive the+// original registration call and other Node environments in the same process.+// A Windows temporary's cleanup pipe is closed by process teardown.+static LOADED: Mutex<Option<Loaded>> = Mutex::new(None);+static IMAGE_ANCHOR: u8 = 0;++fn replacement_carrier(+    mode: &CacheMode,+    resolve: impl FnOnce() -> Result<std::path::PathBuf>,+) -> Option<std::path::PathBuf> {+    // APFS self-replacement recompresses the decoded image with ditto and+    // decodes it again for this process. Keep macOS first loads on the verified+    // cache path, even for writable installations, before resolving the carrier.+    if cfg!(target_os = "macos") || *mode == CacheMode::Temporary {+        return None;+    }+    match resolve() {+        Ok(path) => Some(path),+        Err(error) => {+            tracing::debug!(%error, "native carrier path unavailable; using cache");+            None+        }+    }+}++fn initialize() -> Result<napi::Register> {+    let mut loaded = LOADED.lock().map_err(|_| {+        Error::new(+            ErrorKind::Load,+            "native loader initialization lock was poisoned",+        )+    })?;+    if let Some(loaded) = loaded.as_ref() {+        return Ok(loaded.register);+    }+    let payload = Payload::parse(PAYLOAD)?.with_integrity(RuntimeIntegrity::parse(INTEGRITY)?)?;+    let mode = CacheMode::from_env()?;+    let replacement = if let Some(carrier) = replacement_carrier(&mode, || unsafe {+        platform::carrier_path((&IMAGE_ANCHOR as *const u8).cast())+    }) {+        // Compression and replacement are optional. Integrity is rechecked in+        // the cache path; no fallback ever loads an unverified original buffer.+        match replacement::try_replace(&payload, &carrier) {+            Ok(replacement) => replacement,+            Err(error) => {+                tracing::debug!(path = %carrier.display(), %error, "native self-replacement unavailable; using cache");+                None+            }+        }+    } else {+        None+    };+    let mut materialized = match replacement {+        Some(file) => file,+        None => cache::materialize(&payload, &mode)?,+    };+    #[cfg(unix)]+    let library = unsafe {+        libloading::os::unix::Library::open(+            Some(materialized.path()),+            libloading::os::unix::RTLD_NOW | libloading::os::unix::RTLD_LOCAL,+        )+        .map(Library::from)+    };+    #[cfg(windows)]+    let library = unsafe { Library::new(materialized.path()) };+    let library = library.map_err(|e| {+        Error::new(+            ErrorKind::Load,+            format!("load verified addon {}: {e}", materialized.path().display()),+        )+    })?;+    let register = *unsafe { library.get::<napi::Register>(b"napi_register_module_v1\0") }+        .map_err(|e| {+            Error::new(+                ErrorKind::Load,+                format!("verified addon has no napi_register_module_v1: {e}"),+            )+        })?;+    if register as usize == napi_register_module_v1 as *const () as usize {+        return Err(Error::new(+            ErrorKind::Load,+            "raw addon registration resolved back to the carrier",+        ));+    }+    materialized.loaded()?;+    *loaded = Some(Loaded {+        register,+        _library: library,+        _materialized: materialized,+    });+    Ok(register)+}++/// The raw packages already target N-API 3, supported by their Node minimums.+#[no_mangle]+pub extern "C" fn node_api_module_get_api_version_v1() -> i32 {+    3+}++/// Forward each environment independently, including a raw initializer+/// returning a different exports value or null with a pending JavaScript+/// exception.+///+/// # Safety+/// Node must pass a live `napi_env` and its associated initial exports value.+#[no_mangle]+pub unsafe extern "C" fn napi_register_module_v1(+    env: napi::Env,+    exports: napi::Value,+) -> napi::Value {+    let error = match std::panic::catch_unwind(initialize) {+        Ok(Ok(register)) => return register(env, exports),+        Ok(Err(error)) => error,+        Err(_) => Error::new(ErrorKind::Load, "native loader initialization panicked"),+    };+    // Successful registration forwards the raw initializer directly. Resolving+    // exception helpers would open and close the process image unnecessarily on+    // every environment; only the failure path needs those symbols.+    let api = match napi::Api::load() {+        Ok(api) => api,+        Err(error) => {+            // This can only occur outside the supported Node ABI: without even+            // napi_throw_error there is no legal way to create a JS exception.+            eprintln!("SWC native addon cannot initialize its N-API host: {error}");+            std::process::abort();+        }+    };+    api.throw(env, &error)+}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn temporary_mode_skips_carrier_resolution() {+        assert!(replacement_carrier(&CacheMode::Temporary, || -> Result<_> {+            panic!("temporary mode must not resolve the carrier")+        })+        .is_none());+    }++    #[test]+    #[cfg(not(target_os = "macos"))]+    fn unavailable_carrier_disables_replacement() {+        assert!(replacement_carrier(&CacheMode::Default, || {+            Err(Error::new(ErrorKind::Load, "carrier is unavailable"))+        })+        .is_none());+    }++    #[test]+    #[cfg(target_os = "macos")]+    fn macos_persistent_modes_skip_carrier_resolution() {+        for mode in [+            CacheMode::Default,+            CacheMode::Custom("/custom/cache".into()),+        ] {+            assert!(replacement_carrier(&mode, || -> Result<_> {+                panic!("macOS must not resolve the carrier for self-replacement")+            })+            .is_none());+        }+    }+}

deps/swc/bindings/binding_native_addon/src/napi.rs

new file mode 100644@@ -0,0 +1,70 @@+//! Only original N-API functions are used here. No generated napi-rs module+//! initialization or newer path-discovery API is linked into the carrier.++use std::ffi::{c_char, c_void, CString};++use swc_native_addon::{Error, ErrorKind, Result};++pub type Env = *mut c_void;+pub type Value = *mut c_void;+pub type Register = unsafe extern "C" fn(Env, Value) -> Value;+type ThrowError = unsafe extern "C" fn(Env, *const c_char, *const c_char) -> i32;+type FatalError = unsafe extern "C" fn(*const c_char, usize, *const c_char, usize);++pub struct Api {+    throw_error: ThrowError,+    fatal_error: FatalError,+}++impl Api {+    pub unsafe fn load() -> Result<Self> {+        #[cfg(unix)]+        let process = libloading::os::unix::Library::this();+        #[cfg(windows)]+        let process = libloading::os::windows::Library::this()+            .map_err(|e| Error::new(ErrorKind::Load, format!("open Node process symbols: {e}")))?;+        let throw_error = *process+            .get::<ThrowError>(b"napi_throw_error\0")+            .map_err(|e| {+                Error::new(+                    ErrorKind::Load,+                    format!("Node must expose napi_throw_error: {e}"),+                )+            })?;+        let fatal_error = *process+            .get::<FatalError>(b"napi_fatal_error\0")+            .map_err(|e| {+                Error::new(+                    ErrorKind::Load,+                    format!("Node must expose napi_fatal_error: {e}"),+                )+            })?;+        Ok(Self {+            throw_error,+            fatal_error,+        })+    }++    pub unsafe fn throw(&self, env: Env, error: &Error) -> Value {+        let code = CString::new(error.kind.code()).unwrap();+        let message = format!(+            "SWC native addon: {error}. Reinstall the native package if its payload is damaged; \+             check SWC_NATIVE_BINDING_CACHE and directory permissions for materialization \+             failures."+        );+        let message = CString::new(message.replace('\0', "?")).unwrap();+        if (self.throw_error)(env, code.as_ptr(), message.as_ptr()) != 0 {+            // A functioning Node host always provides the original throw API.+            // If it cannot create an exception, returning exports would falsely+            // report success. N-API's original fatal API is the last resort.+            let location = b"swc native loader";+            (self.fatal_error)(+                location.as_ptr().cast(),+                location.len(),+                message.as_ptr(),+                message.as_bytes().len(),+            );+        }+        std::ptr::null_mut()+    }+}

deps/swc/bindings/binding_nodejs_support_wasm/Cargo.toml

@@ -6,7 +6,7 @@ license     = { workspace = true } name        = "binding_nodejs_support_wasm" publish     = false repository  = { workspace = true }-version     = "1.16.2"+version     = "1.16.18"  [lib] bench      = false@@ -25,7 +25,9 @@ serde-wasm-bindgen = { workspace = true } serde_json = { workspace = true } swc_common = { path = "../../crates/swc_common" } swc_ecma_ast = { path = "../../crates/swc_ecma_ast" }-swc_ecma_parser = { path = "../../crates/swc_ecma_parser" }+swc_ecma_parser = { path = "../../crates/swc_ecma_parser", features = [+  "unstable",+] } swc_ecma_visit = { path = "../../crates/swc_ecma_visit" } swc_error_reporters = { path = "../../crates/swc_error_reporters" } swc_ts_fast_strip = { path = "../../crates/swc_ts_fast_strip", features = [@@ -36,5 +38,8 @@ unicode-width = { workspace = true } wasm-bindgen = { workspace = true, features = ["enable-interning"] } wasm-bindgen-futures = { workspace = true } +[dev-dependencies]+testing = { path = "../../crates/testing" }+ [package.metadata.wasm-pack.profile.release] wasm-opt = false

deps/swc/bindings/binding_nodejs_support_wasm/README.md

@@ -10,11 +10,53 @@ syntax validation.  ## API -- `transform` and `transformSync`-- `transformModuleSyntax`-- `getFirstExpression`-- `isValidSyntax`-- `isRecoverableError`+-   `transform` and `transformSync`+-   `transformModuleSyntax`+-   `getFirstExpression`+-   `isValidSyntax`+-   `isRecoverableError`+-   `tokenize`+-   `findTopLevelAwaits`++All source arguments accept a string or a UTF-8 `Uint8Array`.++### Syntax highlighting++`tokenize(src)` returns `{ kind, start, end }[]` in source order. The token kinds+are `keyword`, `identifier`, `number`, `bigint`, `string`, `template`, `regexp`,+`comment`, and `punctuator`. Offsets are zero-based UTF-16 code units with an+exclusive end, so `src.slice(start, end)` extracts the token. Comments include+their delimiters; hashbangs are comments. Whitespace and EOF are omitted.+Template text includes its backticks, while `${` and `}` are punctuators and+interpolated expressions have their own tokens.++This helper parses JavaScript to distinguish regular expressions from division+and track template substitutions. On incomplete or invalid input it returns the+tokens captured before parsing stopped, without throwing a syntax error. It is+intended for REPL highlighting, not syntax validation or TypeScript tokenization.++### Top-level await locations++`findTopLevelAwaits(src)` returns `{ line, column }[]` in source order for `await`+expressions, `for await` loops, and `await using` declarations in module scope.+Lines start at 1 and UTF-16 columns start at 0, matching Acorn. Each location is+the start of the construct (`for` for a `for await` loop). Function bodies,+including methods and arrow functions, are excluded; class heritage expressions,+computed keys, and method decorators are included. JavaScript and TypeScript are+supported, and invalid or incomplete modules return an empty array.++### REPL imports++`transformModuleSyntax(src)` hoists imports and declares their local bindings,+so a REPL can retain them for subsequent inputs. For example, a default import+declares `const { default: fs } = await __nodeREPLDynamicImport(...)` after+validating that the export exists. The caller supplies `__nodeREPLDynamicImport`+and evaluates the output with top-level await support.++Default and named bindings capture the initial export values, matching Node's+REPL transform; namespace bindings retain the module namespace object. References+and assignments are left intact so normal JavaScript scope, call, and `const`+semantics apply before any further rewriting performed by the host REPL.  ## Contributing 

deps/swc/bindings/binding_nodejs_support_wasm/src/lib.rs

@@ -31,8 +31,27 @@ export declare function transformModuleSyntax(src: string | Uint8Array): ModuleS export declare function getFirstExpression(src: string | Uint8Array, startColumn: number): string; export declare function isValidSyntax(src: string | Uint8Array): boolean; export declare function isRecoverableError(src: string | Uint8Array): boolean;+export declare function tokenize(src: string | Uint8Array): Token[];+export declare function findTopLevelAwaits(src: string | Uint8Array): TopLevelAwaitLocation[]; export type { Options, TransformOutput }; +export type TokenKind = "keyword" | "identifier" | "number" | "bigint" | "string" | "template" | "regexp" | "comment" | "punctuator";++export interface Token {+    kind: TokenKind;+    /** Zero-based UTF-16 offset, inclusive. */+    start: number;+    /** Zero-based UTF-16 offset, exclusive. */+    end: number;+}++export interface TopLevelAwaitLocation {+    /** One-based line number. */+    line: number;+    /** Zero-based UTF-16 column. */+    column: number;+}+ export interface ModuleSyntaxTransformOutput {     code: string;     hadModuleSyntax: boolean;@@ -91,6 +110,24 @@ pub fn is_recoverable_error(input: JsValue) -> Result<bool, JsValue> {     Ok(nodejs::is_recoverable_error(input)) } +/// Returns compact tokens for syntax highlighting, including a partial prefix+/// when the input is incomplete or invalid.+#[wasm_bindgen(skip_typescript)]+pub fn tokenize(input: JsValue) -> Result<JsValue, JsValue> {+    let input = coerce_input(input)?;+    Ok(serde_wasm_bindgen::to_value(&nodejs::tokenize(&input))?)+}++/// Returns module-scope await locations in source order using Acorn+/// coordinates.+#[wasm_bindgen(js_name = "findTopLevelAwaits", skip_typescript)]+pub fn find_top_level_awaits(input: JsValue) -> Result<JsValue, JsValue> {+    let input = coerce_input(input)?;+    Ok(serde_wasm_bindgen::to_value(+        &nodejs::find_top_level_awaits(&input),+    )?)+}+ fn coerce_input(input: JsValue) -> Result<String, JsValue> {     match input.as_string() {         Some(input) => Ok(input),

deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs.rs

@@ -6,12 +6,9 @@ use serde::Serialize; use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap, Span, Spanned}; use swc_ecma_ast::{-    ArrowExpr, ArrowFunctionBody, AssignExpr, AssignTarget, AssignTargetPat, BindingIdent,-    BlockStmt, CallExpr, Callee, CatchClause, Class, ClassDecl, Decl, DefaultDecl, EsVersion,-    ExportSpecifier, Expr, FnDecl, Function, FunctionBody, ImportDecl, ImportSpecifier,-    MetaPropExpr, MetaPropKind, Module, ModuleDecl, ModuleExportName, ModuleItem, NamedExport,-    ObjectLit, ObjectPatProp, Pat, Prop, SimpleAssignTarget, Stmt, UpdateExpr, VarDecl,-    VarDeclKind,+    CallExpr, Callee, Decl, DefaultDecl, EsVersion, ExportSpecifier, Expr, ImportDecl,+    ImportSpecifier, MetaPropExpr, MetaPropKind, Module, ModuleDecl, ModuleExportName, ModuleItem,+    NamedExport, ObjectLit, }; use swc_ecma_parser::{     error::{Error as ParseError, SyntaxError},@@ -21,6 +18,15 @@ use swc_ecma_parser::{ }; use swc_ecma_visit::{Visit, VisitWith}; +mod awaits;+mod position;+mod tokenize;++#[cfg(test)]+mod fixtures;++pub use self::{awaits::find_top_level_awaits, tokenize::tokenize};+ const DYNAMIC_IMPORT_NAME: &str = "__nodeREPLDynamicImport";  #[derive(Debug, Serialize)]@@ -68,13 +74,6 @@ pub fn transform_module_syntax(code: String) -> ModuleSyntaxTransformOutput {             edits: &mut transform.edits,             replaced_ranges: &replaced_ranges,         });-        module.visit_with(&mut ImportBindingReferenceCollector {-            code: &code,-            edits: &mut transform.edits,-            replaced_ranges: &replaced_ranges,-            bindings: &transform.import_bindings,-            scopes: vec![Vec::new()],-        });          if transform.edits.is_empty() && transform.hoisted.is_empty() {             return ModuleSyntaxTransformOutput {@@ -107,7 +106,7 @@ pub fn get_first_expression(code: String, start_column: u32) -> String {     let mut paren_level = 0u32;     let mut member_bracket_depth = 0u32; -    for token in tokenize(&code) {+    for token in lex_tokens(&code) {         if token.token == Token::Eof {             break;         }@@ -288,8 +287,6 @@ struct ModuleSyntaxTransform<'a> {     code: &'a str,     edits: Vec<Edit>,     hoisted: Vec<String>,-    import_bindings: Vec<ImportBinding>,-    import_namespace_count: u32, }  impl<'a> ModuleSyntaxTransform<'a> {@@ -298,8 +295,6 @@ impl<'a> ModuleSyntaxTransform<'a> {             code,             edits: Vec::new(),             hoisted: Vec::new(),-            import_bindings: Vec::new(),-            import_namespace_count: 0,         }     } @@ -398,7 +393,7 @@ impl<'a> ModuleSyntaxTransform<'a> {         ));     } -    fn import_decl_to_script(&mut self, node: &ImportDecl) -> Option<String> {+    fn import_decl_to_script(&self, node: &ImportDecl) -> Option<String> {         let specifier = node.src.value.to_string_lossy();         let with = node.with.as_deref(); @@ -444,40 +439,37 @@ impl<'a> ModuleSyntaxTransform<'a> {             return None;         } -        let namespace_name = namespace_name.unwrap_or_else(|| self.fresh_import_namespace_name());-        let out = format!(-            "const {namespace_name} = await {};",-            await_import(self.code, &specifier, with, &required_exports)-        );--        if let Some(default_name) = default_name {-            self.import_bindings.push(ImportBinding {-                local: default_name,-                namespace: namespace_name.clone(),-                export_name: "default".to_string(),-            });+        let import = await_import(self.code, &specifier, with, &required_exports);+        if let Some(namespace_name) = namespace_name {+            let mut out = format!("const {namespace_name} = await {import};");+            if let Some(default_name) = default_name {+                out.push_str(&format!(+                    " const {default_name} = {namespace_name}.default;"+                ));+            }+            return Some(out);         } -        for (local, export_name) in named {-            self.import_bindings.push(ImportBinding {-                local,-                namespace: namespace_name.clone(),-                export_name,-            });+        // Declare the user's bindings so subsequent REPL inputs can access them.+        // Like Node's REPL transform, these capture the initial export values;+        // namespace imports still refer to the live module namespace object.+        let mut bindings = Vec::with_capacity(named.len() + usize::from(default_name.is_some()));+        if let Some(default_name) = default_name {+            bindings.push(format!("default: {default_name}"));         }--        Some(out)-    }--    fn fresh_import_namespace_name(&mut self) -> String {-        loop {-            let candidate = format!("__nodeREPLImport{}", self.import_namespace_count);-            self.import_namespace_count += 1;--            if !self.code.contains(&candidate) {-                return candidate;-            }+        for (local, imported) in named {+            // Quote arbitrary export names while keeping ordinary names readable.+            let key = if is_identifier_name(&imported) {+                imported+            } else {+                json_string(&imported)+            };+            bindings.push(format!("{key}: {local}"));         }+        Some(format!(+            "const {{ {} }} = await {import};",+            bindings.join(", ")+        ))     }      fn unwrap_default_declaration(@@ -667,13 +659,6 @@ struct Edit {     text: String, } -#[derive(Debug)]-struct ImportBinding {-    local: String,-    namespace: String,-    export_name: String,-}- fn push_span_edit(edits: &mut Vec<Edit>, code: &str, span: Span, text: String) {     if let Some((start, end)) = span_range(span) {         push_range_edit(edits, code, start, end, text);@@ -779,375 +764,16 @@ impl Visit for ModuleFeatureCollector<'_, '_> {     } } -struct ImportBindingReferenceCollector<'a, 'b> {-    code: &'a str,-    edits: &'b mut Vec<Edit>,-    replaced_ranges: &'b [(usize, usize)],-    bindings: &'b [ImportBinding],-    scopes: Vec<Vec<String>>,-}--impl Visit for ImportBindingReferenceCollector<'_, '_> {-    fn visit_assign_expr(&mut self, node: &AssignExpr) {-        if self.import_binding_for_assign_target(&node.left).is_some() {-            self.replace_expression_with_import_assignment_error(node.span);-            return;-        }--        node.visit_children_with(self);-    }--    fn visit_update_expr(&mut self, node: &UpdateExpr) {-        if let Expr::Ident(ident) = &*node.arg {-            if self.binding_for_unshadowed(ident.sym.as_ref()).is_some() {-                self.replace_expression_with_import_assignment_error(node.span);-                return;-            }-        }--        node.visit_children_with(self);-    }--    fn visit_function(&mut self, node: &Function) {-        let shadowed = function_scope_shadowed_bindings(node, self.bindings);-        self.with_scope(shadowed, |this| node.visit_children_with(this));-    }--    fn visit_arrow_expr(&mut self, node: &ArrowExpr) {-        let shadowed = arrow_scope_shadowed_bindings(node, self.bindings);-        self.with_scope(shadowed, |this| node.visit_children_with(this));-    }--    fn visit_block_stmt(&mut self, node: &BlockStmt) {-        let shadowed = direct_block_shadowed_bindings(node, self.bindings);-        self.with_scope(shadowed, |this| node.visit_children_with(this));-    }--    fn visit_catch_clause(&mut self, node: &CatchClause) {-        let mut shadowed = Vec::new();-        if let Some(param) = &node.param {-            collect_shadowed_pat_bindings(&mut shadowed, param, self.bindings);-        }--        self.with_scope(shadowed, |this| node.visit_children_with(this));-    }--    fn visit_expr(&mut self, node: &Expr) {-        if let Expr::Ident(ident) = node {-            self.replace_identifier(ident.span, ident.sym.as_ref());-            return;-        }--        node.visit_children_with(self);-    }--    fn visit_prop(&mut self, node: &Prop) {-        if let Prop::Shorthand(ident) = node {-            if let Some(binding) = self.binding_for_unshadowed(ident.sym.as_ref()) {-                if let Some((start, end)) = span_range(ident.span) {-                    if !range_is_replaced(self.replaced_ranges, start, end) {-                        let access = import_binding_access(binding);-                        self.edits.push(Edit {-                            start,-                            end,-                            text: format!("{}: {access}", ident.sym),-                        });-                    }-                }-            }-            return;-        }--        node.visit_children_with(self);-    }-}--impl ImportBindingReferenceCollector<'_, '_> {-    fn replace_identifier(&mut self, span: Span, local: &str) {-        let Some(binding) = self.binding_for_unshadowed(local) else {-            return;-        };--        let Some((start, end)) = span_range(span) else {-            return;-        };--        if range_is_replaced(self.replaced_ranges, start, end) {-            return;-        }--        if !self.code.get(start..end).is_some_and(|text| text == local) {-            return;-        }--        self.edits.push(Edit {-            start,-            end,-            text: import_binding_access(binding),-        });-    }--    fn binding_for(&self, local: &str) -> Option<&ImportBinding> {-        self.bindings.iter().find(|binding| binding.local == local)-    }--    fn binding_for_unshadowed(&self, local: &str) -> Option<&ImportBinding> {-        if self.is_shadowed(local) {-            return None;-        }--        self.binding_for(local)-    }--    fn import_binding_for_assign_target(&self, target: &AssignTarget) -> Option<&ImportBinding> {-        match target {-            AssignTarget::Simple(SimpleAssignTarget::Ident(ident)) => {-                self.binding_for_unshadowed(ident.id.sym.as_ref())-            }-            AssignTarget::Pat(pat) => self.import_binding_for_assign_target_pat(pat),-            _ => None,-        }-    }--    fn import_binding_for_assign_target_pat(-        &self,-        target: &AssignTargetPat,-    ) -> Option<&ImportBinding> {-        match target {-            AssignTargetPat::Array(array) => array-                .elems-                .iter()-                .flatten()-                .find_map(|pat| self.import_binding_for_pat(pat)),-            AssignTargetPat::Object(object) => object.props.iter().find_map(|prop| match prop {-                ObjectPatProp::KeyValue(prop) => self.import_binding_for_pat(&prop.value),-                ObjectPatProp::Assign(prop) => {-                    self.binding_for_unshadowed(prop.key.id.sym.as_ref())-                }-                ObjectPatProp::Rest(prop) => self.import_binding_for_pat(&prop.arg),-            }),-            AssignTargetPat::Invalid(..) => None,-        }-    }--    fn import_binding_for_pat(&self, pat: &Pat) -> Option<&ImportBinding> {-        match pat {-            Pat::Ident(ident) => self.binding_for_unshadowed(ident.id.sym.as_ref()),-            Pat::Array(array) => array-                .elems-                .iter()-                .flatten()-                .find_map(|pat| self.import_binding_for_pat(pat)),-            Pat::Rest(rest) => self.import_binding_for_pat(&rest.arg),-            Pat::Object(object) => object.props.iter().find_map(|prop| match prop {-                ObjectPatProp::KeyValue(prop) => self.import_binding_for_pat(&prop.value),-                ObjectPatProp::Assign(prop) => {-                    self.binding_for_unshadowed(prop.key.id.sym.as_ref())-                }-                ObjectPatProp::Rest(prop) => self.import_binding_for_pat(&prop.arg),-            }),-            Pat::Assign(assign) => self.import_binding_for_pat(&assign.left),-            Pat::Invalid(..) | Pat::Expr(..) => None,-        }-    }--    fn replace_expression_with_import_assignment_error(&mut self, span: Span) {-        let Some((start, end)) = span_range(span) else {-            return;-        };--        if range_is_replaced(self.replaced_ranges, start, end) {-            return;-        }--        self.edits.push(Edit {-            start,-            end,-            text: import_assignment_error_expression(),-        });-    }--    fn with_scope(&mut self, shadowed: Vec<String>, op: impl FnOnce(&mut Self)) {-        self.scopes.push(shadowed);-        op(self);-        self.scopes.pop();-    }--    fn is_shadowed(&self, local: &str) -> bool {-        self.scopes-            .iter()-            .rev()-            .any(|scope| scope.iter().any(|name| name == local))-    }-}--fn function_scope_shadowed_bindings(node: &Function, bindings: &[ImportBinding]) -> Vec<String> {-    let mut shadowed = Vec::new();-    for param in &node.params {-        collect_shadowed_pat_bindings(&mut shadowed, &param.pat, bindings);-    }-    if let Some(body) = &node.body {-        collect_function_var_shadowed_bindings(&mut shadowed, body, bindings);-    }-    shadowed-}--fn arrow_scope_shadowed_bindings(node: &ArrowExpr, bindings: &[ImportBinding]) -> Vec<String> {-    let mut shadowed = Vec::new();-    for param in &node.params {-        collect_shadowed_pat_bindings(&mut shadowed, param, bindings);-    }-    if let ArrowFunctionBody::FunctionBody(body) = &*node.body {-        collect_function_var_shadowed_bindings(&mut shadowed, body, bindings);-    }-    shadowed-}--fn collect_function_var_shadowed_bindings(-    out: &mut Vec<String>,-    body: &FunctionBody,-    bindings: &[ImportBinding],-) {-    let mut collector = FunctionScopedVarCollector {-        bindings,-        shadowed: out,-    };-    body.visit_with(&mut collector);-}--struct FunctionScopedVarCollector<'a, 'b> {-    bindings: &'a [ImportBinding],-    shadowed: &'b mut Vec<String>,-}--impl Visit for FunctionScopedVarCollector<'_, '_> {-    fn visit_var_decl(&mut self, node: &VarDecl) {-        if node.kind == VarDeclKind::Var {-            collect_shadowed_var_decl_bindings(self.shadowed, node, self.bindings);-        }-    }--    fn visit_function(&mut self, _: &Function) {}--    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}--    fn visit_class(&mut self, _: &Class) {}-}--fn direct_block_shadowed_bindings(node: &BlockStmt, bindings: &[ImportBinding]) -> Vec<String> {-    let mut shadowed = Vec::new();--    for stmt in &node.stmts {-        if let Stmt::Decl(decl) = stmt {-            collect_shadowed_decl_bindings(&mut shadowed, decl, bindings);-        }-    }--    shadowed-}--fn collect_shadowed_decl_bindings(out: &mut Vec<String>, decl: &Decl, bindings: &[ImportBinding]) {-    match decl {-        Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {-            push_shadowed_ident_binding(out, ident.sym.as_ref(), bindings);-        }-        Decl::Var(var) => {-            collect_shadowed_var_decl_bindings(out, var, bindings);-        }-        Decl::Using(using) => {-            for declarator in &using.decls {-                collect_shadowed_pat_bindings(out, &declarator.name, bindings);-            }-        }-        Decl::TsInterface(..) | Decl::TsTypeAlias(..) | Decl::TsEnum(..) | Decl::TsModule(..) => {}-    }-}--fn collect_shadowed_var_decl_bindings(-    out: &mut Vec<String>,-    decl: &VarDecl,-    bindings: &[ImportBinding],-) {-    for declarator in &decl.decls {-        collect_shadowed_pat_bindings(out, &declarator.name, bindings);-    }-}--fn collect_shadowed_pat_bindings(out: &mut Vec<String>, pat: &Pat, bindings: &[ImportBinding]) {-    match pat {-        Pat::Ident(ident) => push_shadowed_binding_ident(out, ident, bindings),-        Pat::Array(array) => {-            for elem in array.elems.iter().flatten() {-                collect_shadowed_pat_bindings(out, elem, bindings);-            }-        }-        Pat::Rest(rest) => collect_shadowed_pat_bindings(out, &rest.arg, bindings),-        Pat::Object(object) => {-            for prop in &object.props {-                match prop {-                    ObjectPatProp::KeyValue(prop) => {-                        collect_shadowed_pat_bindings(out, &prop.value, bindings);-                    }-                    ObjectPatProp::Assign(prop) => {-                        push_shadowed_binding_ident(out, &prop.key, bindings);-                    }-                    ObjectPatProp::Rest(prop) => {-                        collect_shadowed_pat_bindings(out, &prop.arg, bindings);-                    }-                }-            }-        }-        Pat::Assign(assign) => collect_shadowed_pat_bindings(out, &assign.left, bindings),-        Pat::Invalid(..) | Pat::Expr(..) => {}-    }-}--fn push_shadowed_binding_ident(-    out: &mut Vec<String>,-    ident: &BindingIdent,-    bindings: &[ImportBinding],-) {-    push_shadowed_ident_binding(out, ident.id.sym.as_ref(), bindings);-}--fn push_shadowed_ident_binding(out: &mut Vec<String>, local: &str, bindings: &[ImportBinding]) {-    if bindings.iter().any(|binding| binding.local == local)-        && !out.iter().any(|name| name == local)-    {-        out.push(local.to_string());-    }-}- fn range_is_replaced(replaced_ranges: &[(usize, usize)], start: usize, end: usize) -> bool {     replaced_ranges         .iter()         .any(|(replace_start, replace_end)| start >= *replace_start && end <= *replace_end) } -fn import_binding_access(binding: &ImportBinding) -> String {-    format!(-        "{}{}",-        binding.namespace,-        export_property_access(&binding.export_name)-    )-}--fn export_property_access(export_name: &str) -> String {-    if is_identifier_name(export_name) {-        format!(".{export_name}")-    } else {-        format!("[{}]", json_string(export_name))-    }-}- fn import_meta_error_expression() -> String {     "(() => { throw new SyntaxError(\"Cannot use import.meta outside a module\"); })()".to_string() } -fn import_assignment_error_expression() -> String {-    "(() => { throw new TypeError(\"Assignment to constant variable.\"); })()".to_string()-}- fn is_identifier_name(value: &str) -> bool {     let mut chars = value.chars();     let Some(first) = chars.next() else {@@ -1233,7 +859,7 @@ fn parses_as_program(code: &str) -> bool {     matches!(parse_program_result(code), ParseOutcome::Valid) } -fn tokenize(code: &str) -> Vec<TokenAndSpan> {+fn lex_tokens(code: &str) -> Vec<TokenAndSpan> {     let cm = Lrc::new(SourceMap::default());     let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());     let comments = SingleThreadedComments::default();@@ -1268,7 +894,7 @@ fn is_recoverable_parse_error(code: &str, error: &ParseError) -> bool {             true         }         SyntaxError::UnterminatedStrLit => has_trailing_line_continuation(code),-        SyntaxError::Expected(_, got) => got == "<eof>",+        SyntaxError::Expected(_, got) | SyntaxError::Unexpected { got, .. } => got == "<eof>",         _ => false,     } }@@ -1387,14 +1013,14 @@ mod tests {         assert_eq!(             transform_module_syntax("import fs from \"node:fs\";".into()).code,             format!(-                "const __nodeREPLImport0 = await {};",+                "const {{ default: fs }} = await {};",                 validated_import("node:fs", &["default"])             )         );         assert_eq!(             transform_module_syntax("import { readFile as rf } from \"node:fs\";".into()).code,             format!(-                "const __nodeREPLImport0 = await {};",+                "const {{ readFile: rf }} = await {};",                 validated_import("node:fs", &["readFile"])             )         );@@ -1402,35 +1028,35 @@ mod tests {             transform_module_syntax("import { readFile as rf } from \"node:fs\";\nrf();".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\n__nodeREPLImport0.readFile();",+                "const {{ readFile: rf }} = await {};\nrf();",                 validated_import("node:fs", &["readFile"])             )         );         assert_eq!(             transform_module_syntax("import def, * as ns from \"mod\";".into()).code,             format!(-                "const ns = await {};",+                "const ns = await {}; const def = ns.default;",                 validated_import("mod", &["default"])             )         );         assert_eq!(             transform_module_syntax("import def, * as ns from \"mod\";\ndef; ns;".into()).code,             format!(-                "const ns = await {};\nns.default; ns;",+                "const ns = await {}; const def = ns.default;\ndef; ns;",                 validated_import("mod", &["default"])             )         );         assert_eq!(             transform_module_syntax("import { \"a-b\" as c } from \"mod\";".into()).code,             format!(-                "const __nodeREPLImport0 = await {};",+                "const {{ \"a-b\": c }} = await {};",                 validated_import("mod", &["a-b"])             )         );         assert_eq!(             transform_module_syntax("import { \"a-b\" as c } from \"mod\";\nc;".into()).code,             format!(-                "const __nodeREPLImport0 = await {};\n__nodeREPLImport0[\"a-b\"];",+                "const {{ \"a-b\": c }} = await {};\nc;",                 validated_import("mod", &["a-b"])             )         );@@ -1440,7 +1066,7 @@ mod tests {             )             .code,             format!(-                "const __nodeREPLImport0 = await {};",+                "const {{ default: data }} = await {};",                 validated_import_with_options(                     "./data.json",                     Some("{ with: { type: \"json\" } }"),@@ -1452,8 +1078,7 @@ mod tests {             transform_module_syntax("console.log(typeof fs);\nimport fs from \"node:fs\";".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\nconsole.log(typeof \-                 __nodeREPLImport0.default);\n",+                "const {{ default: fs }} = await {};\nconsole.log(typeof fs);\n",                 validated_import("node:fs", &["default"])             )         );@@ -1532,8 +1157,7 @@ mod tests {         assert_eq!(             transform_module_syntax("import { spec } from \"m\";\nimport(spec);".into()).code,             format!(-                "const __nodeREPLImport0 = await \-                 {};\n__nodeREPLDynamicImport(__nodeREPLImport0.spec);",+                "const {{ spec: spec }} = await {};\n__nodeREPLDynamicImport(spec);",                 validated_import("m", &["spec"])             )         );@@ -1544,8 +1168,7 @@ mod tests {             )             .code,             format!(-                "const __nodeREPLImport0 = await {};\nfunction f(x) {{ return x; \-                 }}\n__nodeREPLImport0.x;",+                "const {{ x: x }} = await {};\nfunction f(x) {{ return x; }}\nx;",                 validated_import("m", &["x"])             )         );@@ -1554,7 +1177,7 @@ mod tests {             transform_module_syntax("import { x } from \"m\";\n{ const x = 1; x; }\nx;".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\n{{ const x = 1; x; }}\n__nodeREPLImport0.x;",+                "const {{ x: x }} = await {};\n{{ const x = 1; x; }}\nx;",                 validated_import("m", &["x"])             )         );@@ -1563,8 +1186,7 @@ mod tests {             transform_module_syntax("import { x } from \"m\";\ntry {} catch (x) { x; }\nx;".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\ntry {{}} catch (x) {{ x; \-                 }}\n__nodeREPLImport0.x;",+                "const {{ x: x }} = await {};\ntry {{}} catch (x) {{ x; }}\nx;",                 validated_import("m", &["x"])             )         );@@ -1576,8 +1198,8 @@ mod tests {             )             .code,             format!(-                "const __nodeREPLImport0 = await {};\nfunction f(){{ if (ok) {{ var x = 1; }} \-                 return x; }}\n__nodeREPLImport0.x;",+                "const {{ x: x }} = await {};\nfunction f(){{ if (ok) {{ var x = 1; }} return x; \+                 }}\nx;",                 validated_import("m", &["x"])             )         );@@ -1586,8 +1208,7 @@ mod tests {             transform_module_syntax("import { readFile } from \"node:fs\";\nreadFile = 1;".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\n(() => {{ throw new TypeError(\"Assignment \-                 to constant variable.\"); }})();",+                "const {{ readFile: readFile }} = await {};\nreadFile = 1;",                 validated_import("node:fs", &["readFile"])             )         );@@ -1598,8 +1219,7 @@ mod tests {             )             .code,             format!(-                "const __nodeREPLImport0 = await {};\n((() => {{ throw new TypeError(\"Assignment \-                 to constant variable.\"); }})());",+                "const {{ readFile: readFile }} = await {};\n({{ readFile }} = obj);",                 validated_import("node:fs", &["readFile"])             )         );@@ -1608,8 +1228,7 @@ mod tests {             transform_module_syntax("import { readFile } from \"node:fs\";\nreadFile++;".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\n(() => {{ throw new TypeError(\"Assignment \-                 to constant variable.\"); }})();",+                "const {{ readFile: readFile }} = await {};\nreadFile++;",                 validated_import("node:fs", &["readFile"])             )         );@@ -1643,8 +1262,8 @@ mod tests {             "#!/usr/bin/env node\nimport fs from \"node:fs\";\nfs.readFile;".into(),         )         .code;-        assert!(hashbang.starts_with("#!/usr/bin/env node\nconst __nodeREPLImport0 = await "));-        assert!(hashbang.contains("\n__nodeREPLImport0.default.readFile;"));+        assert!(hashbang.starts_with("#!/usr/bin/env node\nconst { default: fs } = await "));+        assert!(hashbang.contains("\nfs.readFile;"));          assert_eq!(             transform_module_syntax("foo()\nimport \"x\";\n[1].forEach(bar)".into()).code,@@ -1658,7 +1277,7 @@ mod tests {             transform_module_syntax("import fs from \"node:fs\";\nconst x: number = 1;".into())                 .code,             format!(-                "const __nodeREPLImport0 = await {};\nconst x: number = 1;",+                "const {{ default: fs }} = await {};\nconst x: number = 1;",                 validated_import("node:fs", &["default"])             )         );

deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/awaits.rs

new file mode 100644@@ -0,0 +1,82 @@+use serde::Serialize;+use swc_common::Spanned;+use swc_ecma_ast::{ArrowExpr, AwaitExpr, Constructor, ForOfStmt, Function, UsingDecl};+use swc_ecma_visit::{Visit, VisitWith};++use super::{parse_module, position::SourcePosition, span_range};++/// An Acorn-compatible location: one-based line and zero-based UTF-16 column.+#[derive(Debug, Serialize)]+pub struct TopLevelAwaitLocation {+    pub line: usize,+    pub column: usize,+}++/// Finds await expressions, for-await loops, and await-using declarations in+/// module scope. Invalid or incomplete modules have no diagnostic locations.+pub fn find_top_level_awaits(code: &str) -> Vec<TopLevelAwaitLocation> {+    let Some(module) = parse_module(code) else {+        return Vec::new();+    };+    let mut collector = AwaitCollector::default();+    module.visit_with(&mut collector);+    collector.positions.sort_unstable();++    let mut position = SourcePosition::new(code);+    collector+        .positions+        .into_iter()+        .map(|byte| {+            position.advance_to(byte);+            TopLevelAwaitLocation {+                line: position.line,+                column: position.column,+            }+        })+        .collect()+}++#[derive(Default)]+struct AwaitCollector {+    positions: Vec<usize>,+}++impl AwaitCollector {+    fn record(&mut self, node: &impl Spanned) {+        if let Some((start, _)) = span_range(node.span()) {+            self.positions.push(start);+        }+    }+}++impl Visit for AwaitCollector {+    fn visit_await_expr(&mut self, node: &AwaitExpr) {+        self.record(node);+        node.visit_children_with(self);+    }++    fn visit_for_of_stmt(&mut self, node: &ForOfStmt) {+        if node.is_await {+            self.record(node);+        }+        node.visit_children_with(self);+    }++    fn visit_using_decl(&mut self, node: &UsingDecl) {+        if node.is_await {+            self.record(node);+        }+        node.visit_children_with(self);+    }++    fn visit_function(&mut self, node: &Function) {+        // Method decorators are evaluated in the enclosing scope, unlike the+        // function's parameters and body. Computed keys are visited separately+        // by the default traversal of the containing method.+        node.decorators.visit_with(self);+    }++    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}++    fn visit_constructor(&mut self, _: &Constructor) {}+}

deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/fixtures.rs

new file mode 100644@@ -0,0 +1,57 @@+use std::{fs, path::PathBuf};++use serde::Deserialize;+use serde_json::Value;++use super::{+    find_top_level_awaits, is_recoverable_error, parse_program_result, tokenize, ParseOutcome,+    SyntaxError,+};++#[derive(Deserialize)]+struct Case {+    source: String,+    expected: Value,+}++fn check_cases(path: PathBuf, run: impl Fn(String) -> Value) {+    let cases: Vec<Case> = serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap();+    for case in cases {+        assert_eq!(+            run(case.source.clone()),+            case.expected,+            "source: {:?}",+            case.source+        );+    }+}++#[testing::fixture("__tests__/fixtures/tokenize/*.json")]+fn tokens(path: PathBuf) {+    check_cases(path, |source| {+        serde_json::to_value(tokenize(&source)).unwrap()+    });+}++#[testing::fixture("__tests__/fixtures/awaits/*.json")]+fn awaits(path: PathBuf) {+    check_cases(path, |source| {+        serde_json::to_value(find_top_level_awaits(&source)).unwrap()+    });+}++#[testing::fixture("__tests__/fixtures/recoverable/*.json")]+fn recoverable(path: PathBuf) {+    check_cases(path, |source| Value::Bool(is_recoverable_error(source)));+}++#[testing::fixture("__tests__/fixtures/recoverable/unexpected-eof.json")]+fn unexpected_eof_variant(path: PathBuf) {+    check_cases(path, |source| {+        let ParseOutcome::Invalid(errors) = parse_program_result(&source) else {+            panic!("expected a parse error: {source:?}");+        };+        assert!(errors.iter().any(|error| matches!(error.kind(), SyntaxError::Unexpected { got, .. } if got == "<eof>")), "errors: {errors:?}");+        Value::Bool(true)+    });+}

deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/position.rs

new file mode 100644@@ -0,0 +1,40 @@+/// Converts monotonically increasing byte positions into JavaScript locations.+/// Walking only the intervening text keeps batches of locations linear in the+/// source length, including when many tokens share the same line.+pub(super) struct SourcePosition<'a> {+    source: &'a str,+    byte: usize,+    pub offset: usize,+    pub line: usize,+    pub column: usize,+    previous_cr: bool,+}++impl<'a> SourcePosition<'a> {+    pub fn new(source: &'a str) -> Self {+        Self {+            source,+            byte: 0,+            offset: 0,+            line: 1,+            column: 0,+            previous_cr: false,+        }+    }++    pub fn advance_to(&mut self, byte: usize) {+        for ch in self.source[self.byte..byte].chars() {+            self.offset += ch.len_utf16();+            match ch {+                '\n' if self.previous_cr => {}+                '\r' | '\n' | '\u{2028}' | '\u{2029}' => {+                    self.line += 1;+                    self.column = 0;+                }+                _ => self.column += ch.len_utf16(),+            }+            self.previous_cr = ch == '\r';+        }+        self.byte = byte;+    }+}

deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/tokenize.rs

new file mode 100644@@ -0,0 +1,122 @@+use serde::Serialize;+use swc_common::{comments::SingleThreadedComments, BytePos, Spanned};+use swc_ecma_ast::EsVersion;+use swc_ecma_parser::{+    unstable::{Capturing, Token as ParserToken},+    Lexer, Parser, StringInput, Syntax,+};++use super::{es_syntax, position::SourcePosition, span_range};++/// The compact token categories consumed by Node's REPL highlighter.+#[derive(Clone, Copy, Debug, Serialize)]+#[serde(rename_all = "lowercase")]+pub enum TokenKind {+    Keyword,+    Identifier,+    Number,+    Bigint,+    String,+    Template,+    Regexp,+    Comment,+    Punctuator,+}++/// A half-open range in the original JavaScript string, measured in UTF-16.+#[derive(Debug, Serialize)]+pub struct Token {+    pub kind: TokenKind,+    pub start: usize,+    pub end: usize,+}++/// Tokenizes a JavaScript REPL input, retaining the captured prefix on errors.+/// The parser drives lexing so regex literals and template substitutions are+/// rescanned with their grammatical context; a bare lexer cannot distinguish+/// these from division operators and ordinary closing braces.+pub fn tokenize(code: &str) -> Vec<Token> {+    let comments = SingleThreadedComments::default();+    let lexer = Capturing::new(Lexer::new(+        Syntax::Es(es_syntax()),+        EsVersion::latest(),+        StringInput::new(code, BytePos(1), BytePos(code.len() as u32 + 1)),+        Some(&comments),+    ));+    let mut parser = Parser::new_from(lexer);+    let result = parser.parse_program();+    let end_of_prefix = result+        .err()+        .into_iter()+        .chain(parser.take_errors())+        .filter_map(|error| span_range(error.span()).map(|(start, _)| start))+        .min()+        .unwrap_or(code.len());+    let captured = parser.input_mut().iter_mut().take();+    let mut tokens = Vec::with_capacity(captured.len());++    for token in captured {+        let Some((start, end)) = span_range(token.span) else {+            continue;+        };+        // Error tokens have no public category. Do not color their contents as+        // punctuation or include any speculative tokens beyond that point.+        if token.token == ParserToken::Error || start >= end_of_prefix {+            break;+        }+        let kind = match token.token {+            ParserToken::Eof => continue,+            ParserToken::Str => TokenKind::String,+            ParserToken::Num => TokenKind::Number,+            ParserToken::BigInt => TokenKind::Bigint,+            ParserToken::Regex => TokenKind::Regexp,+            ParserToken::Shebang => TokenKind::Comment,+            ParserToken::TemplateHead | ParserToken::TemplateMiddle | ParserToken::TemplateTail => {+                // Head/middle tokens include `${`; the closing `}` has its+                // own captured token and is excluded from the rescan's span.+                if token.token == ParserToken::TemplateTail {+                    push_token(&mut tokens, TokenKind::Template, start, end);+                } else {+                    push_token(&mut tokens, TokenKind::Template, start, end - 2);+                    push_token(&mut tokens, TokenKind::Punctuator, end - 2, end);+                }+                continue;+            }+            ParserToken::NoSubstitutionTemplateLiteral+            | ParserToken::Template+            | ParserToken::BackQuote => TokenKind::Template,+            token if token.is_keyword() => TokenKind::Keyword,+            token if token == ParserToken::Ident || token.is_known_ident() => TokenKind::Identifier,+            _ => TokenKind::Punctuator,+        };+        push_token(&mut tokens, kind, start, end);+    }++    let (leading, trailing) = comments.take_all();+    for map in [leading, trailing] {+        for comment in map.borrow().values().flatten() {+            if let Some((start, end)) = span_range(comment.span) {+                if end <= end_of_prefix {+                    push_token(&mut tokens, TokenKind::Comment, start, end);+                }+            }+        }+    }+    tokens.sort_unstable_by_key(|token| (token.start, token.end));+    tokens.dedup_by_key(|token| (token.start, token.end));++    let mut position = SourcePosition::new(code);+    for token in &mut tokens {+        position.advance_to(token.start);+        token.start = position.offset;+        position.advance_to(token.end);+        token.end = position.offset;+    }+    tokens+}++fn push_token(tokens: &mut Vec<Token>, kind: TokenKind, start: usize, end: usize) {+    if start < end {+        tokens.push(Token { kind, start, end });+    }+}

deps/swc/bindings/binding_typescript_wasm/Cargo.toml

@@ -6,7 +6,7 @@ license     = { workspace = true } name        = "binding_typescript_wasm" publish     = false repository  = { workspace = true }-version     = "1.16.2"+version     = "1.16.18"  [lib] bench      = false

deps/swc/bindings/swc_cli/Cargo.toml

@@ -6,7 +6,7 @@ include     = ["Cargo.toml", "src/**/*.rs"] license     = { workspace = true } name        = "swc_cli" repository  = { workspace = true }-version     = "0.123.2"+version     = "0.127.7"  [[bin]] bench = false@@ -20,4 +20,4 @@ plugin  = ["swc_cli_impl/plugin"] [dependencies] anyhow       = { workspace = true } par-core     = { workspace = true, features = ["chili"] }-swc_cli_impl = { version = "78.0.0", path = "../../crates/swc_cli_impl" }+swc_cli_impl = { version = "82.0.0", path = "../../crates/swc_cli_impl" }

deps/swc/crates/binding_macros/Cargo.toml

@@ -5,7 +5,7 @@ edition     = { workspace = true } license     = { workspace = true } name        = "binding_macros" repository  = { workspace = true }-version     = "76.0.0"+version     = "80.0.0"  [lib] bench = false@@ -33,10 +33,10 @@ binding_wasm = [  [dependencies] # Common deps for the SWC imports-swc                 = { optional = true, version = "76.0.0", path = "../swc", default-features = false }+swc                 = { optional = true, version = "80.0.0", path = "../swc", default-features = false } swc_common          = { optional = true, version = "26.0.0", path = "../swc_common", default-features = false }-swc_ecma_ast        = { optional = true, version = "29.0.0", path = "../swc_ecma_ast", default-features = false }-swc_ecma_transforms = { optional = true, version = "62.0.0", path = "../swc_ecma_transforms", default-features = false }+swc_ecma_ast        = { optional = true, version = "29.0.2", path = "../swc_ecma_ast", default-features = false }+swc_ecma_transforms = { optional = true, version = "64.0.0", path = "../swc_ecma_transforms", default-features = false } swc_ecma_visit      = { optional = true, version = "29.0.0", path = "../swc_ecma_visit", default-features = false }  # Optional deps for the wasm binding macro

deps/swc/crates/dbg-swc/Cargo.toml

@@ -5,7 +5,7 @@ edition     = { workspace = true } license     = { workspace = true } name        = "dbg-swc" repository  = { workspace = true }-version     = "62.0.0"+version     = "64.0.0"  [[bin]] bench = false@@ -26,16 +26,16 @@ swc_common = { version = "26.0.0", features = [   "concurrent",   "tty-emitter", ], path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_minifier = { version = "61.0.5", path = "../swc_ecma_minifier", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_minifier = { version = "63.0.1", path = "../swc_ecma_minifier", features = [   "concurrent", ] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", features = [   "flow", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_typescript = { version = "55.0.2", path = "../swc_ecma_transforms_typescript" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_typescript = { version = "56.0.0", path = "../swc_ecma_transforms_typescript" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_error_reporters = { version = "28.0.0", path = "../swc_error_reporters" } tempfile = { workspace = true }

deps/swc/crates/hstr/Cargo.toml

@@ -5,7 +5,7 @@ edition     = { workspace = true } license     = { workspace = true } name        = "hstr" repository  = { workspace = true }-version     = "4.0.1"+version     = "4.0.2"  [lib] bench = false

deps/swc/crates/hstr/src/wtf8/mod.rs

@@ -605,16 +605,7 @@ impl Wtf8 {     /// Returns `true` if this WTF-8 string starts with the given UTF-8 string.     #[inline]     pub fn starts_with(&self, pattern: &str) -> bool {-        if pattern.len() > self.len() {-            return false;-        }--        let pattern_wtf8 = self.slice_to(pattern.len());-        if let Some(pattern_str) = pattern_wtf8.as_str() {-            pattern_str == pattern-        } else {-            false-        }+        self.as_bytes().starts_with(pattern.as_bytes())     }      /// Try to convert the string to UTF-8 and return a `&str` slice.@@ -1330,6 +1321,16 @@ mod tests {         );     } +    #[test]+    fn wtf8_starts_with() {+        assert!(Wtf8::from_str("aé 💩").starts_with("aé"));+        assert!(!Wtf8::from_str("aé 💩").starts_with("a💩"));+        assert!(!Wtf8::from_str("aé 💩").starts_with("aé 💩!"));++        // The pattern length falls inside the first non-ASCII code point.+        assert!(!Wtf8::from_str("/example/path/to/日本語").starts_with("next/dist/compiled"));+    }+     #[test]     fn wtf8_from_bytes_unchecked() {         assert_eq!(unsafe { &Wtf8::from_bytes_unchecked(b"").bytes }, b"");

deps/swc/crates/jsdoc/Cargo.toml

@@ -33,5 +33,5 @@ swc_common = { version = "26.0.0", path = "../swc_common" }  [dev-dependencies] dashmap         = { workspace = true }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" } testing         = { version = "27.0.0", path = "../testing" }

deps/swc/crates/preset_env_base/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "preset_env_base" repository    = { workspace = true }-version       = "9.0.0"+version       = "10.0.0"  [lib] bench = false

deps/swc/crates/swc-ast-explorer/Cargo.toml

@@ -19,8 +19,8 @@ clap = { version = "3", features = ["derive"] } owo-colors = { workspace = true } regex = { workspace = true } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" } swc_error_reporters = { version = "28.0.0", path = "../swc_error_reporters" }  [dev-dependencies]

deps/swc/crates/swc/Cargo.toml

@@ -9,7 +9,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc" repository    = { workspace = true }-version       = "76.0.0"+version       = "80.0.0"  [lib] bench = false@@ -92,10 +92,10 @@ swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap",   "parking_lot", ] }-swc_compiler_base = { version = "64.0.0", path = "../swc_compiler_base" }-swc_config = { version = "6.0.0", path = "../swc_config" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_compiler_base = { version = "66.0.0", path = "../swc_compiler_base" }+swc_config = { version = "6.0.1", path = "../swc_config" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_ext_transforms = { version = "35.0.0", path = "../swc_ecma_ext_transforms" } swc_ecma_lints = { version = "37.0.0", path = "../swc_ecma_lints", optional = true } swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader", features = [@@ -103,26 +103,26 @@ swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader", features =   "node",   "tsc", ] }-swc_ecma_minifier = { version = "61.0.5", path = "../swc_ecma_minifier" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_minifier = { version = "63.0.1", path = "../swc_ecma_minifier" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_preset_env = { version = "63.0.0", path = "../swc_ecma_preset_env", default-features = false, features = [+swc_ecma_preset_env = { version = "66.0.0", path = "../swc_ecma_preset_env", default-features = false, features = [   "serde-impl", ] }-swc_ecma_react_compiler = { version = "25.0.0", path = "../swc_ecma_react_compiler", optional = true }-swc_ecma_transforms = { version = "62.0.0", path = "../swc_ecma_transforms", features = [+swc_ecma_react_compiler = { version = "26.0.0", path = "../swc_ecma_react_compiler", optional = true }+swc_ecma_transforms = { version = "64.0.0", path = "../swc_ecma_transforms", features = [   "compat",   "optimization",   "proposal",   "react",   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_compat = { version = "57.0.1", path = "../swc_ecma_transforms_compat", default-features = false }-swc_ecma_transforms_module = { version = "55.0.1", path = "../swc_ecma_transforms_module", optional = true }-swc_ecma_transforms_optimization = { version = "52.0.0", path = "../swc_ecma_transforms_optimization" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_compat = { version = "58.0.0", path = "../swc_ecma_transforms_compat", default-features = false }+swc_ecma_transforms_module = { version = "57.0.0", path = "../swc_ecma_transforms_module", optional = true }+swc_ecma_transforms_optimization = { version = "53.0.0", path = "../swc_ecma_transforms_optimization" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_error_reporters = { version = "28.0.0", path = "../swc_error_reporters" } swc_node_comments = { version = "27.0.0", path = "../swc_node_comments" }@@ -130,7 +130,7 @@ swc_plugin_backend_wasmer = { version = "17.0.0", path = "../swc_plugin_backend_ swc_plugin_proxy = { version = "30.0.0", path = "../swc_plugin_proxy", optional = true } swc_plugin_runner = { version = "34.0.0", path = "../swc_plugin_runner", optional = true, default-features = false } swc_transform_common = { version = "20.0.0", path = "../swc_transform_common" }-swc_typescript = { version = "34.0.0", path = "../swc_typescript", optional = true }+swc_typescript = { version = "34.0.1", path = "../swc_typescript", optional = true } swc_visit = { version = "2.0.2", path = "../swc_visit" }  [dependencies.tokio]@@ -156,20 +156,20 @@ par-core                  = { workspace = true, features = ["chili"] } tempfile                  = { workspace = true } walkdir                   = { workspace = true } -swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "serde-impl", ] } swc_ecma_lints = { version = "37.0.0", path = "../swc_ecma_lints", features = [   "non_critical_lints", ] }-swc_ecma_preset_env = { version = "63.0.0", path = "../swc_ecma_preset_env", features = [+swc_ecma_preset_env = { version = "66.0.0", path = "../swc_ecma_preset_env", features = [   "es3", ] }-swc_ecma_testing = { version = "27.0.0", path = "../swc_ecma_testing" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_testing = { version = "27.0.1", path = "../swc_ecma_testing" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] }-swc_ecma_transforms_compat = { version = "57.0.1", path = "../swc_ecma_transforms_compat", features = [+swc_ecma_transforms_compat = { version = "58.0.0", path = "../swc_ecma_transforms_compat", features = [   "es3", ] } swc_malloc = { version = "1.2.5", path = "../swc_malloc" }

deps/swc/crates/swc/src/config/mod.rs

@@ -283,6 +283,31 @@ impl Options {          cfg.merge(config.unwrap_or_default()); +        let base_dir = self.root.clone().unwrap_or_else(|| {+            #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]+            {+                self.cwd.clone()+            }+            #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]+            {+                PathBuf::new()+            }+        });++        #[cfg(all(+            feature = "module",+            not(all(target_arch = "wasm32", not(target_os = "wasi")))+        ))]+        if let Some(crate::config::ModuleConfig::Amd(amd)) = &mut cfg.module {+            if let Some(root) = &amd.module_root {+                let root_path = Path::new(root);+                if root_path.is_relative() {+                    let joined = base_dir.join(root_path);+                    amd.module_root = Some(joined.to_string_lossy().to_string());+                }+            }+        }+         if let FileName::Real(base) = base {             cfg.adjust(base);         }@@ -642,6 +667,7 @@ impl Options {             base,             cfg.module.as_ref(),             preserve_symlinks,+            &base_dir,         );          let target = es_version;@@ -1674,10 +1700,28 @@ impl ModuleConfig {         base: &FileName,         config: Option<&ModuleConfig>,         preserve_symlinks: bool,+        base_dir: &Path,     ) -> Option<(FileName, Arc<dyn ImportResolver>)> {         let skip_resolver = base_url.as_os_str().is_empty() && paths.is_empty();          if skip_resolver {+            let has_module_root = match config {+                Some(ModuleConfig::Amd(amd)) => amd.module_root.is_some(),+                _ => false,+            };+            if has_module_root {+                if let FileName::Real(v) = base {+                    let v = if v.is_absolute() {+                        v.clone()+                    } else {+                        base_dir.join(v)+                    };+                    return Some((+                        FileName::Real(v),+                        Arc::new(swc_ecma_transforms_module::path::NoopImportResolver),+                    ));+                }+            }             return None;         } @@ -1762,6 +1806,7 @@ impl ModuleConfig {         _base: &FileName,         _config: Option<&ModuleConfig>,         _preserve_symlinks: bool,+        _base_dir: &Path,     ) -> Option<(FileName, Arc<dyn swc_ecma_loader::resolve::Resolve>)> {         None     }

deps/swc/crates/swc/src/lib.rs

@@ -813,6 +813,20 @@ impl Compiler {                                 })?                             };                         }++                        #[cfg(all(+                            feature = "module",+                            not(all(target_arch = "wasm32", not(target_os = "wasi")))+                        ))]+                        if let Some(crate::config::ModuleConfig::Amd(amd)) = &mut c.module {+                            if let Some(root) = &amd.module_root {+                                let root_path = Path::new(root);+                                if root_path.is_relative() {+                                    let joined = dir.join(root_path);+                                    amd.module_root = Some(joined.to_string_lossy().to_string());+                                }+                            }+                        }                     }                      return Ok(config);

deps/swc/crates/swc_atoms/Cargo.toml

@@ -22,4 +22,4 @@ once_cell     = { workspace = true } serde         = { workspace = true } shrink-to-fit = { workspace = true, optional = true } -hstr = { version = "4.0.1", path = "../hstr", features = ["serde"] }+hstr = { version = "4.0.2", path = "../hstr", features = ["serde"] }

deps/swc/crates/swc_bundler/Cargo.toml

@@ -9,7 +9,7 @@ include       = ["Cargo.toml", "build.rs", "src/**/*.rs", "src/**/*.js"] license       = { workspace = true } name          = "swc_bundler" repository    = { workspace = true }-version       = "56.0.1"+version       = "57.0.0"  [package.metadata.docs.rs] all-features = true@@ -42,15 +42,15 @@ tracing       = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_optimization = { version = "52.0.0", path = "../swc_ecma_transforms_optimization" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_optimization = { version = "53.0.0", path = "../swc_ecma_transforms_optimization" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_graph_analyzer = { version = "14.0.2", path = "../swc_graph_analyzer/" } @@ -67,14 +67,14 @@ swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader", features =   "node",   "cache", ] }-swc_ecma_minifier = { version = "61.0.5", path = "../swc_ecma_minifier", features = [+swc_ecma_minifier = { version = "63.0.1", path = "../swc_ecma_minifier", features = [   "concurrent", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] }-swc_ecma_transforms_proposal = { version = "49.0.1", path = "../swc_ecma_transforms_proposal" }-swc_ecma_transforms_react = { version = "55.0.1", path = "../swc_ecma_transforms_react" }-swc_ecma_transforms_typescript = { version = "55.0.2", path = "../swc_ecma_transforms_typescript" }+swc_ecma_transforms_proposal = { version = "50.0.0", path = "../swc_ecma_transforms_proposal" }+swc_ecma_transforms_react = { version = "56.0.0", path = "../swc_ecma_transforms_react" }+swc_ecma_transforms_typescript = { version = "56.0.0", path = "../swc_ecma_transforms_typescript" } swc_malloc = { version = "1.2.5", path = "../swc_malloc" } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_cli_impl/Cargo.toml

@@ -6,7 +6,7 @@ include     = ["Cargo.toml", "src/**/*.rs"] license     = { workspace = true } name        = "swc_cli_impl" repository  = { workspace = true }-version     = "78.0.0"+version     = "82.0.0"  [[bin]] name = "swc"@@ -37,7 +37,7 @@ serde_json         = { workspace = true, features = ["unbounded_depth"] } tracing            = { workspace = true } walkdir            = { workspace = true } -swc_core = { version = "78.0.0", features = [+swc_core = { version = "82.0.2", features = [   "common_concurrent",   "base_concurrent",   "base_flow",@@ -49,3 +49,4 @@ swc_core = { version = "78.0.0", features = [ [dev-dependencies] assert_cmd = { workspace = true } assert_fs  = { workspace = true }+base64     = { workspace = true }

deps/swc/crates/swc_cli_impl/src/commands/compile.rs

@@ -15,7 +15,7 @@ use glob::Pattern; use par_iter::prelude::*; use path_absolutize::Absolutize; use pathdiff::diff_paths;-use paths::resolve_output_path;+use paths::{resolve_output_path, resolve_source_file_name}; use swc_core::{     base::{         config::{@@ -685,8 +685,16 @@ impl CompileOptions {         &self,         compiler: Arc<Compiler>,         file_path: &Path,+        output_file_path: &Path,     ) -> anyhow::Result<TransformOutput> {-        let options = self.build_transform_options(&Some(file_path))?;+        let mut options = self.build_transform_options(&Some(file_path))?;+        options.output_path = Some(output_file_path.to_path_buf());+        if options.source_file_name.is_none() {+            // Output paths are absolute, but source filenames may be relative.+            // Normalize only the source-map name: loading an absolute input+            // would also change JSX development filenames and plugin metadata.+            options.source_file_name = Some(resolve_source_file_name(file_path, output_file_path)?);+        }         let fm = compiler             .cm             .load_file(file_path)@@ -703,7 +711,6 @@ impl CompileOptions {     ) -> anyhow::Result<()> {         match entry.kind {             OutputEntryKind::Compile => {-                let output = self.transform_path(compiler, &entry.path)?;                 let output_file_path = resolve_output_path(                     out_dir,                     &self.files,@@ -711,6 +718,7 @@ impl CompileOptions {                     Some(&self.out_file_extension),                     self.strip_leading_paths,                 )?;+                let output = self.transform_path(compiler, &entry.path, &output_file_path)?;                  emit_directory_output(output, &output_file_path)             }

deps/swc/crates/swc_cli_impl/src/commands/compile/paths.rs

@@ -23,6 +23,33 @@ pub fn resolve_output_path(     Ok(out_dir.join(relative_path)) } +/// Resolve a source-map source relative to the output file's directory without+/// changing the input filename or resolving symlinks.+pub fn resolve_source_file_name(+    file_path: &Path,+    output_file_path: &Path,+) -> anyhow::Result<String> {+    let file_path = file_path.absolutize()?;+    let output_file_path = output_file_path.absolutize()?;+    let output_dir = output_file_path+        .parent()+        .context("output file should have a parent directory")?;++    // Different Windows drives or UNC roots require an absolute source path.+    let source = if file_path.components().next() == output_dir.components().next() {+        diff_paths(file_path.as_ref(), output_dir).unwrap_or_else(|| file_path.into_owned())+    } else {+        file_path.into_owned()+    };+    let source = source.to_string_lossy();++    Ok(if cfg!(windows) {+        source.replace('\\', "/")+    } else {+        source.into_owned()+    })+}+ fn resolve_relative_output_path(     raw_inputs: &[PathBuf],     file_path: &Path,

deps/swc/crates/swc_compiler_base/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_compiler_base" repository    = { workspace = true }-version       = "64.0.0"+version       = "66.0.0"  [features] node = ["napi", "napi-derive"]@@ -27,11 +27,11 @@ swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap", ] }-swc_config = { version = "6.0.0", path = "../swc_config" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_minifier = { version = "61.0.5", path = "../swc_ecma_minifier" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_config = { version = "6.0.1", path = "../swc_config" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_minifier = { version = "63.0.1", path = "../swc_ecma_minifier" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_config/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_config" repository    = { workspace = true }-version       = "6.0.0"+version       = "6.0.1"   [package.metadata.docs.rs]

deps/swc/crates/swc_config/src/types/bool_or_data.rs

@@ -120,6 +120,10 @@ where         match value {             Value::Bool(b) => Ok(BoolOr::Bool(b)),             Value::Object(map) if map.is_empty() => Ok(BoolOr::Bool(true)),+            // An object was given, so the error of `T` names the actual problem+            obj @ Value::Object(_) => T::deserialize(obj)+                .map(BoolOr::Data)+                .map_err(serde::de::Error::custom),             other => {                 // Try to deserialize the value as T                 T::deserialize(other)

deps/swc/crates/swc_core/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_core" repository    = { workspace = true }-version       = "78.0.0"+version       = "82.0.2" [package.metadata.docs.rs] features = [   "allocator_node",@@ -333,50 +333,50 @@ __visit = ["__ecma", "swc_ecma_visit"] par-core = { workspace = true, optional = true }  # swc_* dependencies-binding_macros                   = { optional = true, version = "76.0.0", path = "../binding_macros" }-swc                              = { optional = true, version = "76.0.0", path = "../swc", default-features = false }+binding_macros                   = { optional = true, version = "80.0.0", path = "../binding_macros" }+swc                              = { optional = true, version = "80.0.0", path = "../swc", default-features = false } swc_allocator                    = { version = "5.0.0", path = "../swc_allocator", default-features = false } swc_atoms                        = { optional = true, version = "10.0.0", path = "../swc_atoms" }-swc_bundler                      = { optional = true, version = "56.0.1", path = "../swc_bundler" }+swc_bundler                      = { optional = true, version = "57.0.0", path = "../swc_bundler" } swc_common                       = { optional = true, version = "26.0.0", path = "../swc_common" }-swc_config                       = { optional = true, version = "6.0.0", path = "../swc_config" }+swc_config                       = { optional = true, version = "6.0.1", path = "../swc_config" } swc_css_ast                      = { optional = true, version = "26.0.0", path = "../swc_css_ast" } swc_css_codegen                  = { optional = true, version = "26.0.0", path = "../swc_css_codegen" } swc_css_compat                   = { optional = true, version = "26.0.0", path = "../swc_css_compat" } swc_css_minifier                 = { optional = true, version = "26.0.0", path = "../swc_css_minifier" } swc_css_modules                  = { optional = true, version = "26.0.0", path = "../swc_css_modules" } swc_css_parser                   = { optional = true, version = "26.0.0", path = "../swc_css_parser" }-swc_css_prefixer                 = { optional = true, version = "31.0.0", path = "../swc_css_prefixer" }+swc_css_prefixer                 = { optional = true, version = "32.0.0", path = "../swc_css_prefixer" } swc_css_utils                    = { optional = true, version = "26.0.0", path = "../swc_css_utils/" } swc_css_visit                    = { optional = true, version = "26.0.0", path = "../swc_css_visit" }-swc_ecma_ast                     = { optional = true, version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen                 = { optional = true, version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_ast                     = { optional = true, version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen                 = { optional = true, version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_lints                   = { optional = true, version = "37.0.0", path = "../swc_ecma_lints" } swc_ecma_loader                  = { optional = true, version = "27.0.0", path = "../swc_ecma_loader" }-swc_ecma_minifier                = { optional = true, version = "61.0.5", path = "../swc_ecma_minifier" }-swc_ecma_parser                  = { optional = true, version = "45.1.1", path = "../swc_ecma_parser", default-features = false }-swc_ecma_preset_env              = { optional = true, version = "63.0.0", path = "../swc_ecma_preset_env" }-swc_ecma_quote_macros            = { optional = true, version = "45.0.1", path = "../swc_ecma_quote_macros" }-swc_ecma_react_compiler          = { optional = true, version = "25.0.0", path = "../swc_ecma_react_compiler" }-swc_ecma_transforms_base         = { optional = true, version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_compat       = { optional = true, version = "57.0.1", path = "../swc_ecma_transforms_compat" }-swc_ecma_transforms_module       = { optional = true, version = "55.0.1", path = "../swc_ecma_transforms_module" }-swc_ecma_transforms_optimization = { optional = true, version = "52.0.0", path = "../swc_ecma_transforms_optimization" }-swc_ecma_transforms_proposal     = { optional = true, version = "49.0.1", path = "../swc_ecma_transforms_proposal" }-swc_ecma_transforms_react        = { optional = true, version = "55.0.1", path = "../swc_ecma_transforms_react" }-swc_ecma_transforms_testing      = { optional = true, version = "53.0.0", path = "../swc_ecma_transforms_testing" }-swc_ecma_transforms_typescript   = { optional = true, version = "55.0.2", path = "../swc_ecma_transforms_typescript" }-swc_ecma_utils                   = { optional = true, version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_minifier                = { optional = true, version = "63.0.1", path = "../swc_ecma_minifier" }+swc_ecma_parser                  = { optional = true, version = "46.0.0", path = "../swc_ecma_parser", default-features = false }+swc_ecma_preset_env              = { optional = true, version = "66.0.0", path = "../swc_ecma_preset_env" }+swc_ecma_quote_macros            = { optional = true, version = "46.0.0", path = "../swc_ecma_quote_macros" }+swc_ecma_react_compiler          = { optional = true, version = "26.0.0", path = "../swc_ecma_react_compiler" }+swc_ecma_transforms_base         = { optional = true, version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_compat       = { optional = true, version = "58.0.0", path = "../swc_ecma_transforms_compat" }+swc_ecma_transforms_module       = { optional = true, version = "57.0.0", path = "../swc_ecma_transforms_module" }+swc_ecma_transforms_optimization = { optional = true, version = "53.0.0", path = "../swc_ecma_transforms_optimization" }+swc_ecma_transforms_proposal     = { optional = true, version = "50.0.0", path = "../swc_ecma_transforms_proposal" }+swc_ecma_transforms_react        = { optional = true, version = "56.0.0", path = "../swc_ecma_transforms_react" }+swc_ecma_transforms_testing      = { optional = true, version = "54.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_transforms_typescript   = { optional = true, version = "56.0.0", path = "../swc_ecma_transforms_typescript" }+swc_ecma_utils                   = { optional = true, version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit                   = { optional = true, version = "29.0.0", path = "../swc_ecma_visit" } swc_malloc                       = { optional = true, version = "1.2.5", path = "../swc_malloc" }-swc_node_bundler                 = { optional = true, version = "77.0.0", path = "../swc_node_bundler" }+swc_node_bundler                 = { optional = true, version = "81.0.0", path = "../swc_node_bundler" } swc_nodejs_common                = { optional = true, version = "1.0.3", path = "../swc_nodejs_common" } swc_plugin                       = { optional = true, version = "1.0.1", path = "../swc_plugin" } swc_plugin_macro                 = { optional = true, version = "2.0.0", path = "../swc_plugin_macro" } swc_plugin_proxy                 = { optional = true, version = "30.0.0", path = "../swc_plugin_proxy" } swc_plugin_runner                = { optional = true, version = "34.0.0", path = "../swc_plugin_runner", default-features = false } swc_transform_common             = { optional = true, version = "20.0.0", path = "../swc_transform_common" }-swc_typescript                   = { optional = true, version = "34.0.0", path = "../swc_typescript" }+swc_typescript                   = { optional = true, version = "34.0.1", path = "../swc_typescript" } testing                          = { optional = true, version = "27.0.0", path = "../testing" }  [dev-dependencies]

deps/swc/crates/swc_css/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_css" repository    = { workspace = true }-version       = "31.0.0"+version       = "32.0.0"  [package.metadata.docs.rs] all-features = true@@ -28,6 +28,6 @@ swc_css_compat   = { version = "26.0.0", path = "../swc_css_compat", optional = swc_css_minifier = { version = "26.0.0", path = "../swc_css_minifier", optional = true } swc_css_modules  = { version = "26.0.0", path = "../swc_css_modules", optional = true } swc_css_parser   = { version = "26.0.0", path = "../swc_css_parser" }-swc_css_prefixer = { version = "31.0.0", path = "../swc_css_prefixer", optional = true }+swc_css_prefixer = { version = "32.0.0", path = "../swc_css_prefixer", optional = true } swc_css_utils    = { version = "26.0.0", path = "../swc_css_utils/" } swc_css_visit    = { version = "26.0.0", path = "../swc_css_visit" }

deps/swc/crates/swc_css_lints/Cargo.toml

@@ -23,7 +23,7 @@ thiserror   = { workspace = true }  swc_atoms     = { version = "10.0.0", path = "../swc_atoms" } swc_common    = { version = "26.0.0", path = "../swc_common" }-swc_config    = { version = "6.0.0", path = "../swc_config", features = ["regex"] }+swc_config    = { version = "6.0.1", path = "../swc_config", features = ["regex"] } swc_css_ast   = { version = "26.0.0", path = "../swc_css_ast" } swc_css_visit = { version = "26.0.0", path = "../swc_css_visit" } 

deps/swc/crates/swc_css_prefixer/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs", "src/**/*.json", "data/**/*.json"] license       = { workspace = true } name          = "swc_css_prefixer" repository    = { workspace = true }-version       = "31.0.0"+version       = "32.0.0"  [lib] bench = false@@ -18,7 +18,7 @@ rustc-hash = { workspace = true } serde      = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -preset_env_base = { version = "9.0.0", path = "../preset_env_base" }+preset_env_base = { version = "10.0.0", path = "../preset_env_base" } swc_atoms       = { version = "10.0.0", path = "../swc_atoms" } swc_common      = { version = "26.0.0", path = "../swc_common" } swc_css_ast     = { version = "26.0.0", path = "../swc_css_ast" }

deps/swc/crates/swc_ecma_ast/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_ast" repository    = { workspace = true }-version       = "29.0.0"+version       = "29.0.2"  [package.metadata.docs.rs] all-features = true

deps/swc/crates/swc_ecma_ast/src/expr.rs

@@ -1634,7 +1634,7 @@ pub enum SimpleAssignTarget {     #[tag("TsInstantiation")]     TsInstantiation(TsInstantiation), -    #[tag("Invaliid")]+    #[tag("Invalid")]     Invalid(Invalid), } @@ -1864,6 +1864,7 @@ test_de!(             "end": 2,             "ctxt": 0           },+          "ctxt": 0,           "value": "a",           "optional": false         },

deps/swc/crates/swc_ecma_ast/src/lit.rs

@@ -212,15 +212,16 @@ impl Str {         self.value.is_empty()     } +    /// Decodes an ordinary template element's raw text into its string value.+    ///+    /// Normalizes line endings and preserves unpaired UTF-16 surrogates.+    /// Invalid escapes emit diagnostics; tagged templates must preserve+    /// their optional cooked value instead of using this conversion.     pub fn from_tpl_raw(tpl: &TplElement) -> Wtf8Atom {         let tpl_raw = &tpl.raw;         let span = tpl.span;         let mut buf: Wtf8Buf = Wtf8Buf::with_capacity(tpl_raw.len());         let mut iter = tpl_raw.chars();-        // prev_result can only be less than 0xdc00-        // so init with 0xdc00 as no prev result-        const NO_PREV_RESULT: u32 = 0xdc00;-        let mut prev_result: u32 = NO_PREV_RESULT;         while let Some(c) = iter.next() {             match c {                 '\\' => {@@ -255,94 +256,44 @@ impl Str {                             }                             '\n' | '\u{2028}' | '\u{2029}' => {}                             'u' | 'x' => {-                                let mut count: u8 = 0;-                                // result is a 4 digit hex value-                                let mut result: u32 = 0;-                                let mut max_len = if c == 'u' { 4 } else { 2 };+                                let braced = c == 'u' && iter.clone().next() == Some('{');+                                if braced {+                                    iter.next();+                                }+                                let digits = if c == 'u' { 4 } else { 2 };+                                let mut count = 0;+                                let mut result = 0u32;+                                let mut terminated = !braced;                                 for c in &mut iter {-                                    match c {-                                        '{' if max_len == 4 && count == 0 => {-                                            max_len = 6;-                                            continue;-                                        }-                                        '}' if max_len == 6 => {-                                            break;-                                        }-                                        '0'..='9' => {-                                            result = (result << 4) | (c as u32 - '0' as u32);-                                            count += 1;-                                        }-                                        'a'..='f' => {-                                            result = (result << 4) | (c as u32 - 'a' as u32 + 10);-                                            count += 1;-                                        }-                                        'A'..='F' => {-                                            result = (result << 4) | (c as u32 - 'A' as u32 + 10);-                                            count += 1;-                                        }-                                        _ => emit_span_error(+                                    if braced && c == '}' {+                                        terminated = true;+                                        break;+                                    }+                                    let Some(digit) = c.to_digit(16) else {+                                        emit_span_error(                                             span,                                             "Uncaught SyntaxError: Invalid Unicode escape sequence",-                                        ),-                                    }-                                    if count >= max_len {-                                        if result > 0x10ffff {-                                            emit_span_error(-                                                span,-                                                "Uncaught SyntaxError: Undefined Unicode \-                                                 code-point",-                                            )-                                        } else {-                                            break;-                                        }+                                        );+                                        break;+                                    };+                                    // Saturation keeps arbitrarily long invalid escapes from+                                    // overflowing while allowing leading zeroes in valid ones.+                                    result = result.saturating_mul(16).saturating_add(digit);+                                    count += 1;+                                    if !braced && count == digits {+                                        break;                                     }                                 }-                                if max_len == 2 && max_len != count {+                                if !terminated || count == 0 || (!braced && count != digits) {                                     emit_span_error(                                         span,                                         "Uncaught SyntaxError: Invalid hexadecimal escape sequence",                                     );                                 }-                                if (0xd800..=0xdfff).contains(&result) {-                                    // Handle UTF-16 surrogate pair-                                    if result < 0xdc00 {-                                        // High surrogate pair-                                        if prev_result != NO_PREV_RESULT {-                                            // If the previous result is a high surrogate-                                            // We can be sure `prev_result` is less than 0xdc00-                                            buf.push(unsafe {-                                                CodePoint::from_u32_unchecked(prev_result)-                                            });-                                        }-                                        let mut iter = iter.clone();-                                        if let Some('\\') = iter.next() {-                                            if let Some('u') = iter.next() {-                                                // less than 0xdc00-                                                prev_result = result;-                                                continue;-                                            }-                                        }-                                    } else if prev_result != NO_PREV_RESULT {-                                        // Low surrogate pair-                                        // Decode to supplementary plane code point-                                        // (0x10000-0x10FFFF)-                                        result = 0x10000-                                            + ((result & 0x3ff) | ((prev_result & 0x3ff) << 10));-                                        // We can be sure result is a valid code point here-                                        buf.push(unsafe { CodePoint::from_u32_unchecked(result) });-                                        prev_result = NO_PREV_RESULT;-                                        continue;-                                    }-                                }-                                if prev_result != NO_PREV_RESULT {-                                    // Could not find a valid low surrogate pair-                                    // We can be sure `prev_result` is less than 0xdc00-                                    buf.push(unsafe { CodePoint::from_u32_unchecked(prev_result) });-                                    prev_result = NO_PREV_RESULT;-                                }-                                if result <= 0x10ffff {-                                    // We can be sure result is a valid code point here-                                    buf.push(unsafe { CodePoint::from_u32_unchecked(result) });+                                if let Some(code_point) = CodePoint::from_u32(result) {+                                    // Wtf8Buf joins adjacent surrogate pairs and preserves+                                    // unpaired surrogates without lookahead or buffering.+                                    buf.push(code_point);                                 } else {                                     emit_span_error(                                         span,@@ -381,6 +332,13 @@ impl Str {                     }                 } +                '\r' => {+                    if iter.clone().next() == Some('\n') {+                        iter.next();+                    }+                    buf.push_char('\n');+                }+                 c => {                     buf.push_char(c);                 }

deps/swc/crates/swc_ecma_codegen/.gitignore

@@ -1,2 +1,5 @@ /output.js-*.map\ No newline at end of file+*.map++# Keep expected source-map snapshots for the template escape fixtures.+!tests/fixture/template-unicode-escapes/**/output*.map

deps/swc/crates/swc_ecma_codegen/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_codegen" repository    = { workspace = true }-version       = "32.0.0"+version       = "32.0.3"  [features] __testing_tsrx = ["swc_ecma_parser/tsrx"]@@ -37,9 +37,9 @@ swc_allocator = { version = "5.0.0", path = "../swc_allocator", default-features ] } swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_codegen_macros = { version = "2.0.2", path = "../swc_ecma_codegen_macros" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" }  [dev-dependencies] base64                    = { workspace = true }@@ -50,8 +50,8 @@ swc_allocator = { version = "5.0.0", path = "../swc_allocator" } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap", ] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_testing = { version = "27.0.0", path = "../swc_ecma_testing" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_testing = { version = "27.0.1", path = "../swc_ecma_testing" } swc_malloc = { version = "1.2.5", path = "../swc_malloc" } swc_sourcemap = { workspace = true } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_codegen/src/lib.rs

@@ -43,6 +43,7 @@ mod object; mod pat; mod scope_helpers; mod stmt;+mod template; #[cfg(test)] mod tests; pub mod text_writer;@@ -170,7 +171,9 @@ impl Deref for CowStr<'_> {     } } -static NEW_LINE_TPL_REGEX: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"\\n|\n").unwrap());+// Consume other escapes too, so an escaped backslash cannot start a newline.+static NEW_LINE_TPL_REGEX: Lazy<regex::Regex> =+    Lazy::new(|| regex::Regex::new(r"(\\(?:n|x0[aA]|u000[aA]|u\{0*[aA]\}|\n)|\n)|\\.").unwrap());  impl<W, S: SourceMapper> Emitter<'_, W, S> where@@ -1144,73 +1147,9 @@ fn get_template_element_from_raw(     ascii_only: bool,     reduce_escaped_newline: bool, ) -> String {-    fn read_escaped(-        radix: u32,-        len: Option<usize>,-        buf: &mut String,-        iter: impl Iterator<Item = char>,-    ) {-        let mut v = 0;-        let mut pending = None;--        for (i, c) in iter.enumerate() {-            if let Some(len) = len {-                if i == len {-                    pending = Some(c);-                    break;-                }-            }--            match c.to_digit(radix) {-                None => {-                    pending = Some(c);-                    break;-                }-                Some(d) => {-                    v = v * radix + d;-                }-            }-        }--        match radix {-            16 => {-                match v {-                    0 => match pending {-                        Some('1'..='9') => write!(buf, "\\x00").unwrap(),-                        _ => write!(buf, "\\0").unwrap(),-                    },-                    1..=15 => write!(buf, "\\x0{v:x}").unwrap(),-                    // '\x20'..='\x7e'-                    32..=126 => {-                        let c = char::from_u32(v);--                        match c {-                            Some(c) => write!(buf, "{c}").unwrap(),-                            _ => {-                                unreachable!()-                            }-                        }-                    }-                    // '\x10'..='\x1f'-                    // '\u{7f}'..='\u{ff}'-                    _ => {-                        write!(buf, "\\x{v:x}").unwrap();-                    }-                }-            }--            _ => unreachable!(),-        }--        if let Some(pending) = pending {-            buf.push(pending);-        }-    }-     let mut buf = String::with_capacity(s.len());     let mut iter = s.chars().peekable();--    let mut is_dollar_prev = false;+    let mut null_escape_end = None;      while let Some(c) = iter.next() {         let unescape = match c {@@ -1227,36 +1166,36 @@ fn get_template_element_from_raw(                         }                     }                     't' => Some('\t'),-                    'x' => {-                        read_escaped(16, Some(2), &mut buf, &mut iter);--                        None-                    }-                    // TODO handle `\u1111` and `\u{1111}` too+                    'x' | 'u' => match if c == 'x' {+                        template::decode_hex_escape(&mut iter)+                    } else {+                        template::decode_unicode_escape(&mut iter, ascii_only)+                    } {+                        Some(c @ ('\\' | '`')) => {+                            // Decoded delimiters must not become template syntax or escapes.+                            buf.push('\\');+                            Some(c)+                        }+                        Some('\n') if !reduce_escaped_newline => {+                            buf.push_str("\\n");+                            None+                        }+                        Some(c) => Some(c),+                        None => {+                            buf.push('\\');+                            buf.push(c);+                            None+                        }+                    },                     // Source - https://github.com/eslint/eslint/blob/main/lib/rules/no-useless-escape.js                     '\u{2028}' | '\u{2029}' => None,-                    // `\t` and `\h` are special cases, because they can be replaced on real-                    // characters `\xXX` can be replaced on character-                    '\\' | 'r' | 'v' | 'b' | 'f' | 'u' | '\r' | '\n' | '`' | '0'..='7' => {+                    '0' => Some('\0'),+                    '\\' | 'r' | 'v' | 'b' | 'f' | '\r' | '\n' | '`' | '1'..='7' => {                         buf.push('\\');                         buf.push(c);                          None                     }-                    '$' if iter.peek() == Some(&'{') => {-                        buf.push('\\');-                        buf.push('$');--                        None-                    }-                    '{' if is_dollar_prev => {-                        buf.push('\\');-                        buf.push('{');--                        is_dollar_prev = false;--                        None-                    }                     _ => Some(c),                 },                 None => Some('\\'),@@ -1265,27 +1204,33 @@ fn get_template_element_from_raw(         };          match unescape {-            Some(c @ '$') => {-                is_dollar_prev = true;--                buf.push(c);+            Some('{') if buf.ends_with('$') => {+                // Adjacent decoded characters must not introduce an interpolation.+                buf.push_str("\\{");             }             Some('\x00') => {-                let next = iter.peek();--                match next {-                    Some('1'..='9') => buf.push_str("\\x00"),-                    _ => buf.push_str("\\0"),-                }+                buf.push_str("\\0");+                null_escape_end = Some(buf.len());+            }+            Some(c @ '0'..='9') if null_escape_end == Some(buf.len()) => {+                // Decide after decoding the next character: an escaped digit or a+                // removed line continuation can otherwise turn \0 into an octal escape.+                buf.truncate(buf.len() - 2);+                buf.push_str("\\x00");+                buf.push(c);             }             // Octal doesn't supported in template literals, except in tagged templates, but             // we don't use this for tagged templates, they are printing as is             Some('\u{0008}') => buf.push_str("\\b"),             Some('\u{000c}') => buf.push_str("\\f"),             Some('\n') => buf.push('\n'),-            // `\r` is impossible here, because it was removed on parser stage+            // A decoded carriage return must not be normalized to a line feed.+            Some('\r') => buf.push_str("\\r"),             Some('\u{000b}') => buf.push_str("\\v"),             Some('\t') => buf.push('\t'),+            Some(c @ '\x01'..='\x1f') => {+                let _ = write!(buf, "\\x{:02x}", c as u8);+            }             // Print `"` and `'` without quotes             Some(c @ '\x20'..='\x7e') => {                 buf.push(c);@@ -1302,7 +1247,6 @@ fn get_template_element_from_raw(             Some('\u{FEFF}') => {                 buf.push_str("\\uFEFF");             }-            // TODO(kdy1): Surrogate pairs             Some(c) => {                 if !ascii_only || c.is_ascii() {                     buf.push(c);@@ -2292,14 +2236,24 @@ impl MacroNode for TplElement {                 emitter.cfg.ascii_only,                 emitter.cfg.reduce_escaped_newline,             );+            // Decoding escapes can introduce HTML script terminators or comment+            // markers, so protect the emitted text after the transformation.+            let v = if emitter.cfg.inline_script {+                lit::escape_inline_script(&v)+            } else {+                CowStr::Borrowed(&v)+            };             let span = self.span();              let mut last_offset_gen = 0;             let mut last_offset_origin = 0;-            for ((offset_gen, _), mat) in v-                .match_indices('\n')-                .zip(NEW_LINE_TPL_REGEX.find_iter(&raw))-            {+            let reduce_escaped_newline = emitter.cfg.reduce_escaped_newline;+            for ((offset_gen, _), mat) in v.match_indices('\n').zip(+                NEW_LINE_TPL_REGEX+                    .captures_iter(&raw)+                    .filter_map(|c| c.get(1))+                    .filter(|m| reduce_escaped_newline || m.as_str().ends_with('\n')),+            ) {                 // If the string starts with a newline char, then adding a mark is redundant.                 // This catches both "no newlines" and "newline after several chars".                 if offset_gen != 0 {

deps/swc/crates/swc_ecma_codegen/src/lit.rs

@@ -131,16 +131,12 @@ impl MacroNode for Str {             }         } -        let (quote_char, mut value) = get_quoted_utf16(&self.value, emitter.cfg.ascii_only, target);--        if emitter.cfg.inline_script {-            value = CowStr::Owned(-                replace_close_inline_script(&value)-                    .replace("\x3c!--", "\\x3c!--")-                    .replace("--\x3e", "--\\x3e")-                    .into(),-            );-        }+        let (quote_char, value) = get_quoted_utf16(&self.value, emitter.cfg.ascii_only, target);+        let value = if emitter.cfg.inline_script {+            escape_inline_script(&value)+        } else {+            CowStr::Borrowed(&value)+        };          let quote_str = [quote_char.as_byte()];         let quote_str = unsafe {@@ -218,7 +214,23 @@ impl MacroNode for Bool {     } } -pub fn replace_close_inline_script(raw: &str) -> CowStr<'_> {+/// Escape HTML-sensitive sequences after JavaScript string escaping is+/// complete. Borrow the input when no inline-script protection is needed.+pub(crate) fn escape_inline_script(raw: &str) -> CowStr<'_> {+    let value = replace_close_inline_script(raw);+    if value.contains("<!--") || value.contains("-->") {+        CowStr::Owned(+            value+                .replace("<!--", "\\x3c!--")+                .replace("-->", "--\\x3e")+                .into(),+        )+    } else {+        value+    }+}++fn replace_close_inline_script(raw: &str) -> CowStr<'_> {     let chars = raw.as_bytes();     let pattern_len = 8; // </script> @@ -231,7 +243,7 @@ pub fn replace_close_inline_script(raw: &str) -> CowStr<'_> {                 && chars[index + 1..index + pattern_len].eq_ignore_ascii_case(b"/script")                 && matches!(                     chars[index + pattern_len],-                    b'>' | b' ' | b'\t' | b'\n' | b'\x0C' | b'\r'+                    b'>' | b'/' | b' ' | b'\t' | b'\n' | b'\x0C' | b'\r'                 )         })         .map(|(index, _)| index)

deps/swc/crates/swc_ecma_codegen/src/template.rs

new file mode 100644@@ -0,0 +1,64 @@+use std::{iter::Peekable, str::Chars};++/// Decode exactly two hexadecimal digits without consuming the next escape.+pub(crate) fn decode_hex_escape(iter: &mut Peekable<Chars<'_>>) -> Option<char> {+    let mut remaining = iter.clone();+    let high = remaining.next()?.to_digit(16)?;+    let low = remaining.next()?.to_digit(16)?;+    let c = char::from_u32(high * 16 + low)?;+    *iter = remaining;+    Some(c)+}++/// Decode a Unicode escape after its `u`, committing the iterator only when the+/// result can be emitted as a character. Lone surrogates must stay escaped.+pub(crate) fn decode_unicode_escape(+    iter: &mut Peekable<Chars<'_>>,+    ascii_only: bool,+) -> Option<char> {+    let mut remaining = iter.clone();+    let mut value = read_unicode_escape(&mut remaining)?;++    if (0xd800..=0xdbff).contains(&value) {+        if remaining.next()? != '\\' || remaining.next()? != 'u' {+            return None;+        }+        let low = read_unicode_escape(&mut remaining)?;+        if !(0xdc00..=0xdfff).contains(&low) {+            return None;+        }+        value = 0x10000 + ((value - 0xd800) << 10) + low - 0xdc00;+    }++    let c = char::from_u32(value)?;+    if ascii_only && !c.is_ascii() {+        // Keep the original escape instead of expanding it to a braced escape.+        return None;+    }++    *iter = remaining;+    Some(c)+}++fn read_unicode_escape(iter: &mut Peekable<Chars<'_>>) -> Option<u32> {+    let mut value = 0u32;+    if iter.next_if_eq(&'{').is_some() {+        // Read at least one digit; leading zeroes are allowed in braced escapes.+        value = iter.next()?.to_digit(16)?;+        loop {+            let c = iter.next()?;+            if c == '}' {+                return Some(value);+            }+            value = value.checked_mul(16)?.checked_add(c.to_digit(16)?)?;+            if value > 0x10ffff {+                return None;+            }+        }+    }++    for _ in 0..4 {+        value = value * 16 + iter.next()?.to_digit(16)?;+    }+    Some(value)+}

deps/swc/crates/swc_ecma_codegen/src/typescript.rs

@@ -1158,6 +1158,14 @@ impl MacroNode for TsTypeAssertion {         emitter.emit_leading_comments_of_span(self.span(), false)?;          punct!(emitter, "<");+        if matches!(+            &*self.type_ann,+            TsType::TsFnOrConstructorType(TsFnOrConstructorType::TsFnType(function))+                if function.type_params.is_some()+        ) {+            // Keep the assertion and generic parameter openers from becoming `<<`.+            space!(emitter);+        }         emit!(self.type_ann);         punct!(emitter, ">");         emitter.emit_expr_with_precedence(&self.expr, ExprPrecedence::EXPONENTIATION)?;

deps/swc/crates/swc_ecma_compat_bugfixes/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_bugfixes" repository    = { workspace = true }-version       = "56.0.0"+version       = "57.0.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  [dependencies]@@ -16,12 +16,12 @@ tracing    = { workspace = true }  swc_atoms                = { version = "10.0.0", path = "../swc_atoms" } swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_compat_es2015   = { version = "55.0.0", path = "../swc_ecma_compat_es2015" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_compat_es2015   = { version = "56.0.0", path = "../swc_ecma_compat_es2015" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit           = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies]-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }

deps/swc/crates/swc_ecma_compat_common/Cargo.toml

@@ -7,12 +7,12 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_common" repository    = { workspace = true }-version       = "45.0.0"+version       = "46.0.0"  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  [dependencies] swc_common           = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast         = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_utils       = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast         = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_utils       = { version = "35.0.3", path = "../swc_ecma_utils" }

deps/swc/crates/swc_ecma_compat_es2015/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2015" repository    = { workspace = true }-version       = "55.0.0"+version       = "56.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -27,17 +27,17 @@ swc_atoms    = { version = "10.0.0", path = "../swc_atoms" }   swc_common                  = { version = "26.0.0", path = "../swc_common" }-swc_config                  = { version = "6.0.0", path = "../swc_config" }-swc_ecma_ast                = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_compat_common      = { version = "45.0.0", path = "../swc_ecma_compat_common" }-swc_ecma_transformer        = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base    = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_classes = { version = "49.0.0", path = "../swc_ecma_transforms_classes" }+swc_config                  = { version = "6.0.1", path = "../swc_config" }+swc_ecma_ast                = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_compat_common      = { version = "46.0.0", path = "../swc_ecma_compat_common" }+swc_ecma_transformer        = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base    = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_classes = { version = "50.0.0", path = "../swc_ecma_transforms_classes" } swc_ecma_transforms_macros  = { version = "1.0.1", path = "../swc_ecma_transforms_macros" }-swc_ecma_utils              = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils              = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit              = { version = "29.0.0", path = "../swc_ecma_visit" } tracing                     = { workspace = true }  [dev-dependencies]-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }

deps/swc/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs

@@ -78,6 +78,10 @@ impl BlockScopedVars {         self.scope.rename(parent, &mut rename_map, true);         self.scope.rename(parent, &mut rename_map, false); +        // No identifiers change when conflict analysis produces no replacements.+        if rename_map.is_empty() {+            return;+        }         // dbg!(&rename_map);          n.visit_mut_with(@@ -125,6 +129,11 @@ impl Scope {          self.collect_candidates(parent, &mut symbols); +        // Candidate collection consumes usages even when it finds no conflicts.+        // An empty candidate set cannot rename any declaration in this subtree.+        if symbols.is_empty() {+            return;+        }         // dbg!(&symbols);          self.rename_decls(&symbols, rename_map);

deps/swc/crates/swc_ecma_compat_es2015/src/for_of.rs

@@ -1,8 +1,9 @@ use std::mem::take;  use serde::Deserialize;+use smallvec::SmallVec; use swc_atoms::atom;-use swc_common::{util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP};+use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{     helper,@@ -73,10 +74,28 @@ struct ForOf {     top_level_vars: Vec<VarDeclarator>, } +/// Consecutive labels on an iteration statement, ordered outermost first.+#[derive(Default)]+struct LoopLabels(SmallVec<[(Span, Ident); 1]>);++impl LoopLabels {+    fn wrap(self, mut stmt: Stmt) -> Stmt {+        for (span, label) in self.0.into_iter().rev() {+            stmt = LabeledStmt {+                span,+                label,+                body: Box::new(stmt),+            }+            .into();+        }+        stmt+    }+}+ impl ForOf {     fn fold_for_stmt(         &mut self,-        label: Option<Ident>,+        labels: LoopLabels,         ForOfStmt {             span,             left,@@ -206,15 +225,7 @@ impl ForOf {             }             .into(); -            return match label {-                Some(label) => LabeledStmt {-                    span,-                    label,-                    body: Box::new(stmt),-                }-                .into(),-                _ => stmt,-            };+            return labels.wrap(stmt);         }          // Loose mode@@ -328,15 +339,7 @@ impl ForOf {                 body: Box::new(Stmt::Block(body)),             }             .into();-            return match label {-                Some(label) => LabeledStmt {-                    span,-                    label,-                    body: Box::new(stmt),-                }-                .into(),-                _ => stmt,-            };+            return labels.wrap(stmt);         }          let var_span = left.span();@@ -498,15 +501,9 @@ impl ForOf {         }         .into(); -        let for_stmt = match label {-            Some(label) => LabeledStmt {-                span,-                label,-                body: Box::new(for_stmt),-            }-            .into(),-            None => for_stmt,-        };+        // Every loop label must remain inside the try, attached to the iteration+        // statement, so labeled continue targets the loop rather than the try.+        let for_stmt = labels.wrap(for_stmt);          TryStmt {             span: DUMMY_SP,@@ -696,13 +693,20 @@ impl VisitMut for ForOf {      fn visit_mut_stmt(&mut self, s: &mut Stmt) {         match s {-            Stmt::Labeled(LabeledStmt { label, body, .. }) => {-                // Handle label-                match &mut **body {+            Stmt::Labeled(..) => {+                let mut labels = LoopLabels::default();+                let mut body = &mut *s;+                // Only consecutive labels belong to the same iteration+                // statement. A block or another statement ends the chain.+                while let Stmt::Labeled(labeled) = body {+                    labels.0.push((labeled.span, labeled.label.clone()));+                    body = &mut labeled.body;+                }+                match body {                     Stmt::ForOf(stmt) => {                         stmt.visit_mut_children_with(self); -                        *s = self.fold_for_stmt(Some(label.clone()), stmt.take());+                        *s = self.fold_for_stmt(labels, stmt.take());                     }                     _ => {                         body.visit_mut_with(self);@@ -712,7 +716,7 @@ impl VisitMut for ForOf {             Stmt::ForOf(stmt) => {                 stmt.visit_mut_children_with(self); -                *s = self.fold_for_stmt(None, stmt.take())+                *s = self.fold_for_stmt(LoopLabels::default(), stmt.take())             }             _ => s.visit_mut_children_with(self),         }

deps/swc/crates/swc_ecma_compat_es2015/src/template_literal.rs

@@ -61,7 +61,7 @@ impl VisitMut for TemplateLiteral {                         let s = quasis[0]                             .cooked                             .clone()-                            .unwrap_or_else(|| quasis[0].raw.clone().into());+                            .unwrap_or_else(|| Str::from_tpl_raw(&quasis[0]));                          Str {                             span: quasis[0].span,@@ -92,14 +92,15 @@ impl VisitMut for TemplateLiteral {                     let expr = if i % 2 == 0 {                         // Quasis                         match quasis.next() {-                            Some(TplElement {-                                span, cooked, raw, ..-                            }) => {-                                let s = cooked.clone().unwrap_or_else(|| raw.clone().into());+                            Some(quasi) => {+                                let s = quasi+                                    .cooked+                                    .clone()+                                    .unwrap_or_else(|| Str::from_tpl_raw(quasi));                                  Box::new(                                     Lit::Str(Str {-                                        span: *span,+                                        span: quasi.span,                                         value: s,                                         raw: None,                                     })

deps/swc/crates/swc_ecma_compat_es2016/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2016" repository    = { workspace = true }-version       = "50.0.0"+version       = "51.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -16,12 +16,12 @@ version       = "50.0.0" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies]-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } tracing                  = { workspace = true }  [dev-dependencies]-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }

deps/swc/crates/swc_ecma_compat_es2017/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2017" repository    = { workspace = true }-version       = "50.0.0"+version       = "51.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -18,10 +18,10 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] } [dependencies] serde                    = { workspace = true, features = ["derive"] } swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } tracing                  = { workspace = true }  [dev-dependencies]

deps/swc/crates/swc_ecma_compat_es2018/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2018" repository    = { workspace = true }-version       = "51.0.0"+version       = "52.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -17,10 +17,10 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies] serde                    = { workspace = true, features = ["derive"] }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } tracing                  = { workspace = true }  [dev-dependencies]

deps/swc/crates/swc_ecma_compat_es2019/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2019" repository    = { workspace = true }-version       = "50.0.0"+version       = "51.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -17,12 +17,12 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies] swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } tracing                  = { workspace = true }  [dev-dependencies]-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }

deps/swc/crates/swc_ecma_compat_es2020/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2020" repository    = { workspace = true }-version       = "53.0.0"+version       = "54.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -20,11 +20,11 @@ serde   = { workspace = true, features = ["derive"] } tracing = { workspace = true }  swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_compat_es2022   = { version = "53.0.0", path = "../swc_ecma_compat_es2022" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_compat_es2022   = { version = "54.0.0", path = "../swc_ecma_compat_es2022" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit           = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies]

deps/swc/crates/swc_ecma_compat_es2021/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2021" repository    = { workspace = true }-version       = "50.0.0"+version       = "51.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -18,7 +18,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] } [dependencies] tracing = { workspace = true } -swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer     = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer     = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" }

deps/swc/crates/swc_ecma_compat_es2022/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es2022" repository    = { workspace = true }-version       = "53.0.0"+version       = "54.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -22,10 +22,10 @@ tracing    = { workspace = true }  swc_atoms                   = { version = "10.0.0", path = "../swc_atoms" } swc_common                  = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast                = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer        = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms_base    = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_classes = { version = "49.0.0", path = "../swc_ecma_transforms_classes" }+swc_ecma_ast                = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer        = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms_base    = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_classes = { version = "50.0.0", path = "../swc_ecma_transforms_classes" } swc_ecma_transforms_macros  = { version = "1.0.1", path = "../swc_ecma_transforms_macros" }-swc_ecma_utils              = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils              = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit              = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_ecma_compat_es3/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_compat_es3" repository    = { workspace = true }-version       = "41.0.0"+version       = "42.0.0"   # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html@@ -16,11 +16,11 @@ version       = "41.0.0" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies]-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer = { version = "21.0.1", path = "../swc_ecma_transformer", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer = { version = "22.0.0", path = "../swc_ecma_transformer", features = [     "es3", ] }  [dev-dependencies]-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }

deps/swc/crates/swc_ecma_ext_transforms/Cargo.toml

@@ -14,6 +14,6 @@ bench = false [dependencies] phf            = { workspace = true, features = ["macros"] } swc_common     = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast   = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast   = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_ecma_hooks/Cargo.toml

@@ -18,5 +18,5 @@ bench = false [dependencies] swc_atoms      = { version = "10.0.0", path = "../swc_atoms" } swc_common     = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast   = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast   = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_ecma_lexer/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_lexer" repository    = { workspace = true }-version       = "44.0.0"+version       = "45.0.0"  [package.metadata.docs.rs] all-features = true@@ -40,8 +40,8 @@ tracing     = { workspace = true }  swc_atoms       = { version = "10.0.0", path = "../swc_atoms" } swc_common      = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast    = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }+swc_ecma_ast    = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" } swc_ecma_visit  = { version = "29.0.0", path = "../swc_ecma_visit", optional = true }  [target.'cfg(not(any(target_arch = "wasm32", target_arch = "arm")))'.dependencies]@@ -49,7 +49,7 @@ stacker = { version = "0.1.15", optional = true }  [dev-dependencies] -swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "serde-impl", ] } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_ecma_lexer/src/common/parser/module_item.rs

@@ -104,56 +104,59 @@ fn parse_named_export_specifier<'a, P: Parser<'a>>(             // `export { type as }`             // `export { type as as }`             // `export { type as as as }`-            if p.syntax().typescript() && orig_ident.sym == "type" && p.input().cur().is_word() {-                let possibly_orig = parse_ident_name(p).map(Ident::from)?;-                if possibly_orig.sym == "as" {+            // These names may also be string ModuleExportNames.+            if p.syntax().typescript()+                && orig_ident.sym == "type"+                && (p.input().cur().is_word() || p.input().cur().is_str())+            {+                let possibly_orig = parse_module_export_name(p)?;+                if matches!(&possibly_orig, ModuleExportName::Ident(ident) if ident.sym == "as") {                     // `export { type as }`-                    if !p.input().cur().is_word() {+                    if !(p.input().cur().is_word() || p.input().cur().is_str()) {                         if type_only {                             p.emit_err(orig_ident.span, SyntaxError::TS2207);                         }                          return Ok(ExportNamedSpecifier {                             span: p.span(start),-                            orig: ModuleExportName::Ident(possibly_orig),+                            orig: possibly_orig,                             exported: None,                             is_type_only: true,                         });                     } -                    let maybe_as = parse_ident_name(p).map(Ident::from)?;-                    if maybe_as.sym == "as" {-                        if p.input().cur().is_word() {+                    let maybe_as = parse_module_export_name(p)?;+                    if matches!(&maybe_as, ModuleExportName::Ident(ident) if ident.sym == "as") {+                        if p.input().cur().is_word() || p.input().cur().is_str() {                             // `export { type as as as }`                             // `export { type as as foo }`-                            let exported = parse_ident_name(p).map(Ident::from)?;+                            let exported = parse_module_export_name(p)?;                              if type_only {                                 p.emit_err(orig_ident.span, SyntaxError::TS2207);                             } -                            debug_assert!(start <= orig_ident.span.hi());                             return Ok(ExportNamedSpecifier {-                                span: Span::new_with_checked(start, orig_ident.span.hi()),-                                orig: ModuleExportName::Ident(possibly_orig),-                                exported: Some(ModuleExportName::Ident(exported)),+                                span: p.span(start),+                                orig: possibly_orig,+                                exported: Some(exported),                                 is_type_only: true,                             });                         } else {                             // `export { type as as }`                             return Ok(ExportNamedSpecifier {-                                span: Span::new_with_checked(start, orig_ident.span.hi()),+                                span: p.span(start),                                 orig: ModuleExportName::Ident(orig_ident),-                                exported: Some(ModuleExportName::Ident(maybe_as)),+                                exported: Some(maybe_as),                                 is_type_only: false,                             });                         }                     } else {                         // `export { type as xxx }`                         return Ok(ExportNamedSpecifier {-                            span: Span::new_with_checked(start, orig_ident.span.hi()),+                            span: p.span(start),                             orig: ModuleExportName::Ident(orig_ident),-                            exported: Some(ModuleExportName::Ident(maybe_as)),+                            exported: Some(maybe_as),                             is_type_only: false,                         });                     }@@ -165,7 +168,7 @@ fn parse_named_export_specifier<'a, P: Parser<'a>>(                     }                      is_type_only = true;-                    ModuleExportName::Ident(possibly_orig)+                    possibly_orig                 }             } else {                 ModuleExportName::Ident(orig_ident)@@ -213,10 +216,32 @@ fn parse_import_specifier<'a, P: Parser<'a>>(             // Handle:             // `import { type xx } from 'mod'`             // `import { type xx as yy } from 'mod'`+            // `import { type "xx" as yy } from 'mod'`             // `import { type as } from 'mod'`             // `import { type as as } from 'mod'`             // `import { type as as as } from 'mod'`-            if p.syntax().typescript() && orig_name.sym == "type" && p.input().cur().is_word() {+            if p.syntax().typescript()+                && orig_name.sym == "type"+                && (p.input().cur().is_word() || p.input().cur().is_str())+            {+                // A string imported name still requires an identifier local binding.+                if p.input().cur().is_str() {+                    let imported = parse_module_export_name(p)?;+                    expect!(p, &P::Token::AS);+                    let local: Ident = parse_binding_ident(p, false)?.into();++                    if type_only {+                        p.emit_err(orig_name.span, SyntaxError::TS2206);+                    }++                    return Ok(ImportSpecifier::Named(ImportNamedSpecifier {+                        span: Span::new_with_checked(start, local.span.hi()),+                        local,+                        imported: Some(imported),+                        is_type_only: true,+                    }));+                }+                 let possibly_orig_name = parse_ident_name(p).map(Ident::from)?;                 if possibly_orig_name.sym == "as" {                     // `import { type as } from 'mod'`

deps/swc/crates/swc_ecma_lexer/src/common/parser/typescript.rs

@@ -793,16 +793,19 @@ pub fn parse_ts_type_or_type_predicate_ann<'a, P: Parser<'a>>(         }          let type_pred_start = p.input().cur_pos();-        let has_type_pred_asserts = p.input().cur().is_asserts() && {-            let ctx = p.ctx();-            peek!(p).is_some_and(|peek| {-                if peek.is_word() {-                    !peek.is_reserved(ctx)-                } else {-                    false-                }-            })-        };+        // In TypeScript, `asserts` and its parameter must be on the same line.+        let has_type_pred_asserts = p.input().cur().is_asserts()+            && {+                let ctx = p.ctx();+                peek!(p).is_some_and(|peek| {+                    if peek.is_word() {+                        !peek.is_reserved(ctx)+                    } else {+                        false+                    }+                })+            }+            && (p.syntax().flow() || !p.input_mut().has_linebreak_between_cur_and_peeked());          if has_type_pred_asserts {             p.assert_and_bump(&P::Token::ASSERTS);@@ -1525,6 +1528,14 @@ fn parse_ts_tuple_element_type<'a, P: Parser<'a>>(p: &mut P) -> PResult<TsTupleE      let label = try_parse_ts_tuple_element_name(p); +    // Validate the confirmed label outside speculative parsing.+    if let Some(Pat::Rest(rest)) = &label {+        let is_optional = matches!(rest.arg.as_ref(), Pat::Ident(ident) if ident.id.optional);+        if is_optional && !p.input().syntax().flow() {+            syntax_error!(p, rest.span, SyntaxError::TsOptionalRestElement);+        }+    }+     if p.input_mut().eat(&P::Token::DOTDOTDOT) {         let type_ann = parse_ts_type(p)?;         return Ok(TsTupleElement {@@ -1571,22 +1582,22 @@ pub fn parse_ts_tuple_type<'a, P: Parser<'a>>(p: &mut P) -> PResult<TsTupleType>         /* skipFirstToken */ false,     )?; -    // Validate the elementTypes to ensure:-    //   No mandatory elements may follow optional elements-    //   If there's a rest element, it must be at the end of the tuple-+    // No required elements may follow optional elements. Named elements store+    // their optional/rest markers on the label instead of the type.     let mut seen_optional_element = false;      for elem in elems.iter() {-        match *elem.ty {-            TsType::TsRestType(..) => {}-            TsType::TsOptionalType(..) => {-                seen_optional_element = true;-            }-            _ if seen_optional_element => {-                syntax_error!(p, p.span(start), SyntaxError::TsRequiredAfterOptional)-            }-            _ => {}+        if matches!(&elem.label, Some(Pat::Rest(..))) || matches!(*elem.ty, TsType::TsRestType(..))+        {+            continue;+        }++        let is_optional = matches!(&elem.label, Some(Pat::Ident(ident)) if ident.id.optional)+            || matches!(*elem.ty, TsType::TsOptionalType(..));+        if is_optional {+            seen_optional_element = true;+        } else if seen_optional_element {+            syntax_error!(p, p.span(start), SyntaxError::TsRequiredAfterOptional)         }     } @@ -2435,7 +2446,10 @@ fn parse_ts_non_array_type<'a, P: Parser<'a>>(p: &mut P) -> PResult<Box<TsType>>         || cur.is_await()         || cur.is_break()     {-        if p.input().is(&P::Token::ASSERTS) && peek!(p).is_some_and(|peek| peek.is_this()) {+        if p.input().is(&P::Token::ASSERTS)+            && peek!(p).is_some_and(|peek| peek.is_this())+            && (p.syntax().flow() || !p.input_mut().has_linebreak_between_cur_and_peeked())+        {             p.bump();             let this_keyword = parse_ts_this_type_node(p)?;             return parse_ts_this_type_predicate(p, start, true, this_keyword)

deps/swc/crates/swc_ecma_lexer/src/lexer/state.rs

@@ -778,7 +778,7 @@ impl Lexer<'_> {             *start = self.input.cur_pos();         }; -        if self.input.last_pos() == self.input.end_pos() {+        if self.input.as_str().is_empty() {             // End of input.             self.consume_pending_comments();             return Ok(Token::Eof);

deps/swc/crates/swc_ecma_lexer/src/parser/typescript.rs

@@ -1,11 +1,15 @@ #[cfg(test)] mod tests {+    use std::path::PathBuf;++    use swc_common::SourceMap;     use swc_ecma_ast::*;      use crate::{         common::parser::Parser as ParserTrait,+        lexer,         token::{BinOpToken, Token, TokenAndSpan},-        Capturing, Lexer, Parser, Syntax,+        Capturing, Lexer, Parser, StringInput, Syntax, TsSyntax,     };      #[test]@@ -62,4 +66,34 @@ mod tests {         })         .unwrap();     }++    // Reuse the main parser's fixtures for `type` specifier disambiguation.+    #[testing::fixture(+        "../swc_ecma_parser/tests/typescript/custom/type-only/export/type-only-specifier/input.ts"+    )]+    #[testing::fixture(+        "../swc_ecma_parser/tests/typescript/import/type-only-string-specifier/input.ts"+    )]+    fn type_only_specifiers(file: PathBuf) {+        let cm = SourceMap::default();+        let file = cm.load_file(&file).expect("failed to load fixture");+        // Match the TypeScript fixture harness, which disables early errors.+        let syntax = Syntax::Typescript(TsSyntax {+            no_early_errors: true,+            ..Default::default()+        });+        let input = Lexer::new(syntax, Default::default(), StringInput::from(&*file), None);+        let mut parser = Parser::new_from(input.clone());+        let actual = parser.parse_module().expect("specifiers should parse");+        let errors = parser.take_errors();+        assert!(errors.is_empty(), "{errors:?}");++        let mut reference = swc_ecma_parser::Parser::new(syntax, StringInput::from(&*file), None);+        let expected = reference.parse_module().expect("reference should parse");+        assert!(reference.take_errors().is_empty());+        assert_eq!(actual, expected);++        let tokens = lexer(input).expect("public lexer should accept specifiers");+        assert!(!tokens.is_empty());+    } }

deps/swc/crates/swc_ecma_lints/Cargo.toml

@@ -28,17 +28,17 @@ swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "concurrent", ] }-swc_config = { version = "6.0.0", path = "../swc_config" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_config = { version = "6.0.1", path = "../swc_config" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "serde", ] }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] codspeed-criterion-compat = { workspace = true }-swc_ecma_parser           = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_base  = { version = "49.0.1", path = "../swc_ecma_transforms_base" }+swc_ecma_parser           = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_base  = { version = "50.0.0", path = "../swc_ecma_transforms_base" } swc_malloc                = { version = "1.2.5", path = "../swc_malloc" } testing                   = { version = "27.0.0", path = "../testing" } 

deps/swc/crates/swc_ecma_minifier/AGENTS.md

@@ -1,10 +1,19 @@ ### Instructions  - You can run execution tests by doing ./scripts/exec.sh to see if your changes are working.-- If an execution test fails, you are wrong.+- Investigate execution test failures against the semantic assumptions and+  implementation and review policy below. Fix regressions in supported behavior;+  report policy conflicts explicitly instead of hiding failures or arbitrarily+  changing or removing existing tests. - Always run execution tests after making changes. - You can run fixture tests by doing ./scripts/test.sh, and you can do UPDATE=1 ./scripts/test.sh to update fixtures. +### Decorators++- Decorators are lowered before minification and are never present in minifier input.+- Do not add decorator-specific handling or defensive code to the minifier.+- Do not add tests for decorator syntax or decorator transform behavior to minifier test suites. Place these tests in the parser or decorator transform suites instead.+ ### Minifier Semantic Assumptions  The ECMAScript minifier may rely on the documented assumptions from the SWC@@ -31,9 +40,28 @@ minification guide: - Arithmetic expressions are treated as side-effect free, including runtime   exceptions such as mixing `bigint` and `number`. -If a minifier change relies on an assumption beyond this list, update the+#### Implementation and Review Policy++The following is a project policy for minifier implementation and review,+separate from the public documentation assumptions listed above:++- Prioritize correctness for common usage, performance, and simple, maintainable+  minifier code.+- Do not require additional defensive logic or analysis solely to preserve+  extreme edge cases involving monkey-patched globals or built-in objects,+  unusual reassignment of `arguments.length`, bindings introduced by direct+  `eval`, or dynamic name resolution inside `with`.+- Semantic differences that depend only on these cases are not mandatory review+  fixes. A required fix must demonstrate an impact on common usage or identify an+  explicit support contract that the change violates.+- Continue to account for ordinary lexical shadowing, side effects, and+  exceptions within the supported assumptions. This policy does not authorize+  blanket removal of existing `eval` or `with` handling or tests.++If a minifier change relies on an assumption beyond this list and policy, update the public minification documentation or leave an explicit code comment describing-the narrower local invariant.+the narrower local invariant. The edge cases explicitly covered by this policy+do not require repeated, case-specific justification.  ### Fixture Test Addition Guide @@ -41,3 +69,32 @@ the narrower local invariant. - Do not add new regression coverage to tests/terser. Use SWC-owned fixtures such as tests/fixture/issues instead. - Update generated fixture outputs with: UPDATE=1 cargo test -p swc_ecma_minifier. - Verify without UPDATE before finishing: cargo test -p swc_ecma_minifier.++### Terser Fixture Backlog Workflow++`tests/TODO.txt` and `tests/postponed.txt` are equivalent backlogs of ignored+Terser compressor fixtures. Work on exactly one entry from either list at a+time; there is no priority between the lists. An entry such as+`arrays/constant_join_3/input.js` identifies the fixture at+`tests/terser/compress/arrays/constant_join_3`:++- `config.json` defines the Terser compression options to reproduce.+- `input.js` is the source program to minify.+- `output.js` is the expected Terser-compatible output.++Before changing the minifier, delete only the selected entry from the list+where it appears. This activates the existing fixture test; leave every other+backlog entry untouched. Then change the minifier implementation so that the+fixture passes. Do not make a failing fixture pass by changing its Terser+fixture input or expected output.++Verify the activated fixture first with:++```sh+cargo test -p swc_ecma_minifier --test compress fixture_tests__terser__compress__+```++Then run the full crate fixture suite without `UPDATE`, followed by the+execution tests required above. Do not use `scripts/update-list.sh` while+processing an individual entry: it regenerates the lists and can change+unrelated backlog items.

deps/swc/crates/swc_ecma_minifier/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"] license       = { workspace = true } name          = "swc_ecma_minifier" repository    = { workspace = true }-version       = "61.0.5"+version       = "63.0.1"  [package.metadata.docs.rs] all-features = true@@ -53,22 +53,22 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_config = { version = "6.0.0", path = "../swc_config", features = [+swc_config = { version = "6.0.1", path = "../swc_config", features = [   "sourcemap",   "file_pattern",   "regex_js", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "serde", ] }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_hooks = { version = "1.0.0", path = "../swc_ecma_hooks" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_optimization = { version = "52.0.0", path = "../swc_ecma_transforms_optimization" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_optimization = { version = "53.0.0", path = "../swc_ecma_transforms_optimization" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } tracing = { workspace = true } @@ -80,7 +80,7 @@ flate2                    = { workspace = true } humansize                 = { workspace = true } par-core                  = { workspace = true, features = ["chili"] } pretty_assertions         = { workspace = true }-swc_ecma_testing          = { version = "27.0.0", path = "../swc_ecma_testing" }+swc_ecma_testing          = { version = "27.0.1", path = "../swc_ecma_testing" } swc_malloc                = { version = "1.2.5", path = "../swc_malloc" } testing                   = { version = "27.0.0", path = "../testing" } walkdir                   = { workspace = true }

deps/swc/crates/swc_ecma_minifier/fuzz/Cargo.lock

@@ -58,7 +58,7 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"  [[package]] name = "ast_node"-version = "5.0.0"+version = "7.0.0" dependencies = [  "quote",  "swc_macros_common",@@ -353,9 +353,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"  [[package]] name = "foldhash"-version = "0.1.5"+version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"  [[package]] name = "form_urlencoded"@@ -432,21 +432,15 @@ dependencies = [  [[package]] name = "hashbrown"-version = "0.15.5"+version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"+checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" dependencies = [  "allocator-api2",  "equivalent",  "foldhash", ] -[[package]]-name = "hashbrown"-version = "0.16.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"- [[package]] name = "heck" version = "0.5.0"@@ -467,7 +461,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"  [[package]] name = "hstr"-version = "3.0.6"+version = "4.0.2" dependencies = [  "hashbrown 0.14.5",  "new_debug_unreachable",@@ -652,9 +646,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"  [[package]] name = "libfuzzer-sys"-version = "0.4.10"+version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"+checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [  "arbitrary",  "cc",@@ -1017,11 +1011,11 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"  [[package]] name = "regress"-version = "0.10.4"+version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "145bb27393fe455dd64d6cbc8d059adfa392590a45eadf079c01b11857e7b010"+checksum = "158a764437582235e3501f683b93a0a6f8d825d04a789dbe5ed30b8799b8908a" dependencies = [- "hashbrown 0.15.5",+ "hashbrown 0.16.0",  "memchr", ] @@ -1206,7 +1200,7 @@ dependencies = [  [[package]] name = "swc_allocator"-version = "4.0.1"+version = "5.0.0" dependencies = [  "allocator-api2",  "bumpalo",@@ -1216,7 +1210,7 @@ dependencies = [  [[package]] name = "swc_atoms"-version = "9.0.3"+version = "10.0.0" dependencies = [  "hstr",  "once_cell",@@ -1225,7 +1219,7 @@ dependencies = [  [[package]] name = "swc_common"-version = "23.0.2"+version = "26.0.0" dependencies = [  "anyhow",  "arbitrary",@@ -1251,7 +1245,7 @@ dependencies = [  [[package]] name = "swc_config"-version = "5.0.0"+version = "6.0.1" dependencies = [  "anyhow",  "bytes-str",@@ -1280,7 +1274,7 @@ dependencies = [  [[package]] name = "swc_ecma_ast"-version = "25.0.0"+version = "29.0.2" dependencies = [  "arbitrary",  "bitflags",@@ -1299,7 +1293,7 @@ dependencies = [  [[package]] name = "swc_ecma_codegen"-version = "28.0.2"+version = "32.0.3" dependencies = [  "ascii",  "compact_str",@@ -1315,6 +1309,7 @@ dependencies = [  "swc_common",  "swc_ecma_ast",  "swc_ecma_codegen_macros",+ "swc_ecma_utils",  "tracing", ] @@ -1329,7 +1324,7 @@ dependencies = [  [[package]] name = "swc_ecma_hooks"-version = "0.9.0"+version = "1.0.0" dependencies = [  "swc_atoms",  "swc_common",@@ -1339,7 +1334,7 @@ dependencies = [  [[package]] name = "swc_ecma_minifier"-version = "57.0.1"+version = "62.0.1" dependencies = [  "arrayvec",  "bitflags",@@ -1388,7 +1383,7 @@ dependencies = [  [[package]] name = "swc_ecma_parser"-version = "41.1.2"+version = "45.1.6" dependencies = [  "bitflags",  "compact_str",@@ -1407,7 +1402,7 @@ dependencies = [  [[package]] name = "swc_ecma_testing"-version = "24.0.1"+version = "27.0.0" dependencies = [  "anyhow",  "hex",@@ -1418,7 +1413,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_base"-version = "45.0.0"+version = "49.0.1" dependencies = [  "better_scoped_tls",  "indexmap",@@ -1438,7 +1433,7 @@ dependencies = [  [[package]] name = "swc_ecma_transforms_optimization"-version = "48.0.0"+version = "52.0.2" dependencies = [  "bytes-str",  "dashmap",@@ -1447,6 +1442,7 @@ dependencies = [  "par-core",  "petgraph",  "rustc-hash",+ "serde",  "serde_json",  "swc_atoms",  "swc_common",@@ -1460,7 +1456,7 @@ dependencies = [  [[package]] name = "swc_ecma_utils"-version = "31.0.2"+version = "35.0.2" dependencies = [  "dragonbox_ecma",  "indexmap",@@ -1477,7 +1473,7 @@ dependencies = [  [[package]] name = "swc_ecma_visit"-version = "25.0.0"+version = "29.0.0" dependencies = [  "new_debug_unreachable",  "num-bigint",@@ -1499,7 +1495,7 @@ dependencies = [  [[package]] name = "swc_error_reporters"-version = "25.0.0"+version = "28.0.0" dependencies = [  "anyhow",  "miette",@@ -1536,10 +1532,9 @@ dependencies = [  [[package]] name = "swc_visit"-version = "2.0.1"+version = "2.0.2" dependencies = [  "either",- "new_debug_unreachable", ]  [[package]]@@ -1581,7 +1576,7 @@ dependencies = [  [[package]] name = "testing"-version = "24.0.1"+version = "27.0.0" dependencies = [  "cargo_metadata",  "difference",@@ -1600,7 +1595,7 @@ dependencies = [  [[package]] name = "testing_macros"-version = "1.0.1"+version = "1.0.2" dependencies = [  "anyhow",  "glob",

deps/swc/crates/swc_ecma_minifier/package.json

@@ -17,7 +17,7 @@     "devDependencies": {         "@babel/parser": "^7.16.8",         "antd": "^4.16.13",-        "babel-eslint": "^10.1.0",+        "@babel/eslint-parser": "^7.11.0",         "eslint": "^7.29.0",         "readline": "^1.3.0"     }

deps/swc/crates/swc_ecma_minifier/src/compress/hoist_decls.rs

@@ -49,7 +49,6 @@ impl Hoister<'_> {         Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer<ProgramData>>,     {         stmts.visit_mut_children_with(self);-        let len = stmts.len();         let should_hoist = !is_sorted_by(             stmts.iter().map(|stmt| match stmt.as_stmt() {                 Some(stmt) => match stmt {@@ -75,17 +74,7 @@ impl Hoister<'_> {             }),             PartialOrd::partial_cmp,         ) || (self.config.hoist_vars-            && if len >= *crate::LIGHT_TASK_PARALLELS {-                stmts.par_chunks(2).any(|stmts| {-                    is_hoisted_var_decl_without_init(&stmts[0])-                        && is_hoisted_var_decl_without_init(&stmts[1])-                })-            } else {-                stmts.windows(2).any(|stmts| {-                    is_hoisted_var_decl_without_init(&stmts[0])-                        && is_hoisted_var_decl_without_init(&stmts[1])-                })-            });+            && has_adjacent_hoisted_var_decls(stmts, *crate::LIGHT_TASK_PARALLELS));          if !should_hoist {             return;@@ -276,6 +265,21 @@ impl Hoister<'_> {     } } +fn has_adjacent_hoisted_var_decls<T>(stmts: &[T], parallel_threshold: usize) -> bool+where+    T: StmtLike + Sync,+{+    let has_adjacent_hoisted_var_decls = |stmts: &[T]| {+        is_hoisted_var_decl_without_init(&stmts[0]) && is_hoisted_var_decl_without_init(&stmts[1])+    };++    if stmts.len() >= parallel_threshold {+        stmts.par_windows(2).any(has_adjacent_hoisted_var_decls)+    } else {+        stmts.windows(2).any(has_adjacent_hoisted_var_decls)+    }+}+ impl VisitMut for Hoister<'_> {     noop_visit_mut_type!(fail); @@ -287,3 +291,43 @@ impl VisitMut for Hoister<'_> {         self.handle_stmt_likes(stmts);     } }++#[cfg(test)]+mod tests {+    use swc_common::DUMMY_SP;+    use swc_ecma_ast::{+        BindingIdent, EmptyStmt, Ident, Pat, Stmt, VarDecl, VarDeclKind, VarDeclarator,+    };++    use super::has_adjacent_hoisted_var_decls;++    fn uninitialized_var(name: &str) -> Stmt {+        VarDecl {+            span: DUMMY_SP,+            kind: VarDeclKind::Var,+            declare: false,+            decls: vec![VarDeclarator {+                span: DUMMY_SP,+                name: Pat::Ident(BindingIdent::from(Ident::new_no_ctxt(+                    name.into(),+                    DUMMY_SP,+                ))),+                init: None,+                definite: false,+            }],+            ..Default::default()+        }+        .into()+    }++    #[test]+    fn detects_pairs_across_parallel_window_boundaries() {+        let stmts = vec![+            EmptyStmt { span: DUMMY_SP }.into(),+            uninitialized_var("a"),+            uninitialized_var("b"),+        ];++        assert!(has_adjacent_hoisted_var_decls(&stmts, 0));+    }+}

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs

@@ -10,7 +10,7 @@ use swc_ecma_utils::{ use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};  use super::Optimizer;-use crate::compress::optimize::is_left_access_to_arguments;+use crate::{compress::optimize::is_left_access_to_arguments, program_data::VarUsageInfoFlags};  /// Matches Terser's `index < argnames.length + 5` condition. const MAX_INJECTED_PARAMS: usize = 5;@@ -89,20 +89,30 @@ impl Optimizer<'_> {             return;         } -        if f.params.iter().any(|param| match &param.pat {-            Pat::Ident(BindingIdent {-                id: Ident { sym, .. },-                ..-            }) if &**sym == "arguments" => true,-            Pat::Ident(i) => self-                .data-                .vars-                .get(&i.id.to_id())-                .map(|v| v.declared_count >= 2)-                .unwrap_or(false),-            _ => true,-        }) {-            return;+        let mut reassigned_params = Vec::with_capacity(f.params.len());+        for param in &f.params {+            let Pat::Ident(i) = &param.pat else {+                return;+            };++            // A parameter named `arguments` shadows the implicit arguments object.+            if i.id.sym == "arguments" {+                return;+            }++            let Some(usage) = self.data.vars.get(&i.id.to_id()) else {+                reassigned_params.push(false);+                continue;+            };++            // A parameter redeclared in the function body cannot safely replace an+            // arguments access, but reassignment affects only its corresponding+            // arguments index.+            if usage.declared_count >= 2 {+                return;+            }++            reassigned_params.push(usage.flags.contains(VarUsageInfoFlags::REASSIGNED));         }          {@@ -120,6 +130,7 @@ impl Optimizer<'_> {          let mut v = ArgReplacer {             params: &mut f.params,+            reassigned_params,             changed: false,             keep_fargs: self.options.keep_fargs,             prevent: false,@@ -135,6 +146,8 @@ impl Optimizer<'_> {  struct ArgReplacer<'a> {     params: &'a mut Vec<Param>,+    /// Reassigned original parameters, indexed by their `arguments` slot.+    reassigned_params: Vec<bool>,     changed: bool,     keep_fargs: bool,     prevent: bool,@@ -198,6 +211,10 @@ impl VisitMut for ArgReplacer<'_> {             return;         }; +        if self.reassigned_params.get(idx).copied().unwrap_or(false) {+            return;+        }+         self.inject_params_if_within_limit(idx);          if let Some(param) = self.params.get(idx) {

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs

@@ -291,6 +291,7 @@ impl Optimizer<'_> {                                 Some(cur_if) => {                                     if SyntaxContext::within_ignored_ctxt(|| {                                         cur_if.cons.eq_ignore_span(&stmt.cons)+                                            && cur_if.cons.terminates()                                     }) {                                         cur_if.test = BinExpr {                                             span: DUMMY_SP,@@ -1028,6 +1029,12 @@ impl Optimizer<'_> {     where         T: StmtLike,     {+        // Collapsing assignments alone must preserve branch structure. Sequence+        // merging can also use the statements exposed by removing `else`.+        if !self.options.conditionals && !self.options.if_return && !self.options.sequences() {+            return;+        }+         // Find an if statement with else token.         let need_work = stmts.iter().any(|stmt| match stmt.as_stmt() {             Some(Stmt::If(IfStmt {

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs

@@ -39,7 +39,11 @@ impl Optimizer<'_> {         }          // A return statement in a try block may not terminate function.-        if self.ctx.bit_ctx.contains(BitCtx::InTryBlock) {+        if self+            .ctx+            .bit_ctx+            .intersects(BitCtx::InTryCatchWithFinally.union(BitCtx::InTryBlock))+        {             return false;         } @@ -55,9 +59,7 @@ impl Optimizer<'_> {                     .map(|var| {                         var.flags.contains(                             VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL),-                        ) && !(self.data.used_arguments(self.ctx.scope)-                            && var.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM))-                            && !var.flags.intersects(VarUsageInfoFlags::EXPORTED)+                        ) && !var.flags.intersects(VarUsageInfoFlags::EXPORTED)                     })                     .unwrap_or(false)                 {@@ -87,9 +89,7 @@ impl Optimizer<'_> {                         .map(|var| {                             var.flags.contains(                                 VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL),-                            ) && !(self.data.used_arguments(self.ctx.scope)-                                && var.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM))-                                && !var.flags.contains(VarUsageInfoFlags::EXPORTED)+                            ) && !var.flags.contains(VarUsageInfoFlags::EXPORTED)                         })                         .unwrap_or(false)                     {

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs

@@ -1,8 +1,9 @@-use swc_atoms::atom;+use rustc_hash::FxHashSet;+use swc_atoms::{atom, Wtf8Atom}; use swc_common::{util::take::Take, Spanned}; use swc_ecma_ast::*; use swc_ecma_utils::{-    number::{minify_number, JsNumber},+    number::{minify_number, parse_canonical_index, JsNumber, ToJsString},     ExprExt,     Value::Known, };@@ -297,7 +298,11 @@ impl Optimizer<'_> {                 prop: MemberProp::Ident(prop),                 ..             }) => match &**obj {-                Expr::Ident(Ident { sym, .. }) if &**sym == "String" => {+                Expr::Ident(Ident { sym, ctxt, .. }) if &**sym == "String" => {+                    if *ctxt != self.ctx.expr_ctx.unresolved_ctxt {+                        return;+                    }+                     if &*prop.sym == "fromCharCode" {                         if args.len() != 1 {                             return;@@ -330,7 +335,12 @@ impl Optimizer<'_> {                     }                 } -                Expr::Ident(Ident { sym, .. }) if &**sym == "Object" => {+                Expr::Ident(Ident { sym, ctxt, .. })+                    if &**sym == "Object"+                        && self.options.pristine_globals+                        && *ctxt == self.ctx.expr_ctx.unresolved_ctxt+                        && obj.is_global_ref_to(self.ctx.expr_ctx, "Object") =>+                {                     if &*prop.sym == "keys" {                         if args.len() != 1 {                             return;@@ -342,39 +352,36 @@ impl Optimizer<'_> {                         };                          let mut keys = Vec::new();+                        let mut seen = FxHashSet::default();                          for prop in &obj.props {-                            match prop {+                            let (key, key_span) = match prop {                                 PropOrSpread::Spread(_) => return,                                 PropOrSpread::Prop(p) => match &**p {-                                    Prop::Shorthand(p) => {-                                        keys.push(Some(ExprOrSpread {-                                            spread: None,-                                            expr: Lit::Str(Str {-                                                span: p.span,-                                                raw: None,-                                                value: p.sym.clone().into(),-                                            })-                                            .into(),-                                        }));-                                    }+                                    Prop::Shorthand(p) => (p.sym.clone().into(), p.span),                                     Prop::KeyValue(p) => match &p.key {                                         PropName::Ident(key) => {-                                            keys.push(Some(ExprOrSpread {-                                                spread: None,-                                                expr: Lit::Str(Str {-                                                    span: key.span,-                                                    raw: None,-                                                    value: key.sym.clone().into(),-                                                })-                                                .into(),-                                            }));+                                            // A non-computed `__proto__` key-value property sets+                                            // the object's prototype instead of defining an own+                                            // property, so it is not returned by `Object.keys`.+                                            if key.sym == "__proto__" {+                                                continue;+                                            }++                                            (key.sym.clone().into(), key.span)                                         }                                         PropName::Str(key) => {-                                            keys.push(Some(ExprOrSpread {-                                                spread: None,-                                                expr: Lit::Str(key.clone()).into(),-                                            }));+                                            // String-literal `__proto__` key-value properties have+                                            // the same prototype-setter semantics as identifier+                                            // keys. Computed keys remain ineligible for folding.+                                            if key.value.as_str() == Some("__proto__") {+                                                continue;+                                            }++                                            (key.value.clone(), key.span)+                                        }+                                        PropName::Num(key) => {+                                            (key.value.to_js_string().into(), key.span)                                         }                                         _ => return,                                     },@@ -382,10 +389,37 @@ impl Optimizer<'_> {                                 },                                 #[cfg(swc_ast_unknown)]                                 _ => panic!("unable to access unknown nodes"),+                            };++                            if seen.insert(key.clone()) {+                                let index = array_index(&key);+                                keys.push((key, key_span, index));                             }                         } -                        *e = ArrayLit { span, elems: keys }.into()+                        keys.sort_by_key(|(_, _, index)| match index {+                            Some(index) => (0, *index),+                            None => (1, 0),+                        });++                        *e = ArrayLit {+                            span,+                            elems: keys+                                .into_iter()+                                .map(|(value, span, _)| {+                                    Some(ExprOrSpread {+                                        spread: None,+                                        expr: Lit::Str(Str {+                                            span,+                                            raw: None,+                                            value,+                                        })+                                        .into(),+                                    })+                                })+                                .collect(),+                        }+                        .into()                     }                 } @@ -507,6 +541,18 @@ impl Optimizer<'_> {     } } +/// Returns a property key's integer index when it participates in the first+/// group returned by `Object.keys`.+///+/// ECMAScript orders array-index keys before other string keys. `2^32 - 1` is+/// not an array index and must therefore retain its insertion position with+/// the other string keys.+fn array_index(key: &Wtf8Atom) -> Option<u32> {+    parse_canonical_index(key.as_str()?)+        .and_then(|index| u32::try_from(index).ok())+        .filter(|&index| index != u32::MAX)+}+ /// `Math` methods whose folded value can be longer than the call it replaces. /// /// `Math.cos` and friends are deliberately excluded so their existing output is

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/iife.rs

@@ -10,6 +10,7 @@ use super::{util::NormalMultiReplacer, BitCtx, Optimizer}; #[cfg(feature = "debug")] use crate::debug::dump; use crate::{+    compress::optimize::util::may_inline_arrow,     program_data::{ProgramData, ScopeData, VarUsageInfo, VarUsageInfoFlags},     util::{idents_captured_by, make_number}, };@@ -348,6 +349,9 @@ impl Optimizer<'_> {                                     }                                      match &*arg.expr {+                                        // RegExp literals allocate on each evaluation; moving one+                                        // into a returned closure changes its identity.+                                        Expr::Lit(Lit::Regex(..)) => true,                                         Expr::Lit(Lit::Str(s)) if s.value.len() > 3 => true,                                         Expr::Lit(..) => false,                                         _ => true,@@ -474,12 +478,15 @@ impl Optimizer<'_> {             return;         }; +        let first_spread = e.args.iter().position(|arg| arg.spread.is_some());+         for idx in removed {-            if let Some(arg) = e.args.get_mut(idx) {-                if arg.spread.is_some() {-                    break;-                }+            // Arguments at and after a dynamic spread no longer map to parameters by index.+            if matches!(first_spread, Some(first_spread) if idx >= first_spread) {+                break;+            } +            if let Some(arg) = e.args.get_mut(idx) {                 // Optimize                 let new = self.ignore_return_value(&mut arg.expr); @@ -863,7 +870,8 @@ impl Optimizer<'_> {             Expr::Lit(                 Lit::Num(..) | Lit::Str(..) | Lit::Bool(..) | Lit::Null(..) | Lit::BigInt(..),             ) => true,-            Expr::Fn(..) | Expr::Arrow(..) if usage.can_inline_fn_once() => true,+            Expr::Fn(..) if usage.can_inline_fn_once() => true,+            Expr::Arrow(a) if usage.can_inline_fn_once() && may_inline_arrow(a) => true,             _ => false,         }     }@@ -1676,7 +1684,9 @@ impl Optimizer<'_> {             // For arrow functions with simple parameters, inline them             if arrow.params.len() == call.args.len() {                 let can_inline = arrow.params.iter().zip(&call.args).all(|(param, arg)| {-                    param.is_ident() && self.is_simple_expr_for_seq_optimization(&arg.expr)+                    param.is_ident()+                        && self.is_simple_expr_for_seq_optimization(&arg.expr)+                        && !arg.expr.may_have_side_effects(self.ctx.expr_ctx)                 });                  if can_inline {

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/inline.rs

@@ -5,19 +5,21 @@ use swc_atoms::atom; use swc_common::{util::take::Take, EqIgnoreSpan, Mark}; use swc_ecma_ast::*; use swc_ecma_utils::{-    class_has_side_effect, collect_decls, contains_ident_ref, contains_this_expr, find_pat_ids,-    ExprExt, Remapper,+    class_has_side_effect, collect_decls, contains_ident_ref, find_pat_ids, ExprExt, Remapper, }; use swc_ecma_visit::VisitMutWith;  use super::Optimizer; use crate::{-    compress::{-        optimize::{util::is_valid_for_lhs, BitCtx},-        util::contains_super,+    compress::optimize::{+        util::{is_valid_for_lhs, may_inline_arrow},+        BitCtx,     },     program_data::{ScopeData, VarUsageInfo, VarUsageInfoFlags},-    usage_analyzer::alias::{collect_infects_from, AliasConfig},+    usage_analyzer::{+        alias::{collect_infects_from, AliasConfig},+        analyzer::storage::Storage,+    },     util::{         idents_captured_by, idents_used_by, idents_used_by_ignoring_nested, size::SizeWithCtxt,     },@@ -41,7 +43,7 @@ impl Optimizer<'_> {             self.may_remove_ident(ident)         ); -        if let Some(scope) = self.data.get_scope(self.ctx.var_scope) {+        if let Some(scope) = self.data.get_scope(ident.ctxt) {             if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {                 return;             }@@ -52,11 +54,8 @@ impl Optimizer<'_> {             return;         } -        if let Expr::Arrow(ArrowExpr { body, .. }) = init {-            if contains_super(body) {-                return;-            }-            if contains_this_expr(body) {+        if let Expr::Arrow(a) = init {+            if !may_inline_arrow(a) {                 return;             }         }@@ -67,18 +66,14 @@ impl Optimizer<'_> {                 return;             } -            let used_arguments = self-                .data-                .get_scope(self.ctx.var_scope)-                .unwrap()-                .contains(ScopeData::USED_ARGUMENTS);--            if used_arguments-                && usage-                    .flags-                    .contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM)-            {-                return;+            if let Some(scope) = self.data.get_scope(ident.ctxt) {+                if scope.contains(ScopeData::USED_ARGUMENTS)+                    && usage+                        .flags+                        .contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM)+                {+                    return;+                }             }              if usage@@ -584,17 +579,28 @@ impl Optimizer<'_> {             0         } as usize;         let cost_limit = 3 + param_cost + func_body_cost;+        let inline_cost = |expr: &Expr| {+            let mut cost = expr.size(self.ctx.expr_ctx.unresolved_ctxt);+            if let Expr::Object(obj) = expr {+                // Substitution can expand `{ x }` to `{ x: value }`. Count at+                // least the colon and a one-character value before copying it.+                cost += 2 * obj+                    .props+                    .iter()+                    .filter(|prop| {+                        matches!(prop, PropOrSpread::Prop(prop) if matches!(&**prop, Prop::Shorthand(..)))+                    })+                    .count();+            }+            cost+        };          if body.stmts.len() == 1 {             match &body.stmts[0] {-                Stmt::Expr(ExprStmt { expr, .. })-                    if expr.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>-                {-                    return true-                }+                Stmt::Expr(ExprStmt { expr, .. }) if inline_cost(expr) < cost_limit => return true,                  Stmt::Return(ReturnStmt { arg: Some(arg), .. })-                    if arg.size(self.ctx.expr_ctx.unresolved_ctxt) < cost_limit =>+                    if inline_cost(arg) < cost_limit =>                 {                     return true                 }@@ -675,7 +681,7 @@ impl Optimizer<'_> {          let id = i.to_id(); -        if let Some(scope) = self.data.get_scope(self.ctx.var_scope) {+        if let Some(scope) = self.data.get_scope(id.1) {             if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {                 return;             }@@ -722,6 +728,14 @@ impl Optimizer<'_> {                                 usage,                             )                         {+                            if let Some(scope) = self.data.get_scope(f.function.ctxt) {+                                if scope+                                    .intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::IS_ARROW))+                                {+                                    return;+                                }+                            }+                             for (idx, param) in f.function.params.iter().enumerate() {                                 match &param.pat {                                     Pat::Rest(..) => return,@@ -957,6 +971,12 @@ impl Optimizer<'_> {                 remap.insert(id, new_ctxt);             } +            for (from, to) in cache.into_iter() {+                if let Some(scope) = self.data.get_scope(from) {+                    *self.data.scope(to) = *scope+                }+            }+             let mut value = value.clone();             if !remap.is_empty() {                 let mut remapper = Remapper::new(&remap);

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/mod.rs

@@ -10,8 +10,8 @@ use swc_ecma_ast::*; use swc_ecma_transforms_base::rename::contains_eval; use swc_ecma_transforms_optimization::debug_assert_valid; use swc_ecma_utils::{-    prepend_stmts, prop_name_from_ident, ExprCtx, ExprExt, ExprFactory, IsEmpty, ModuleItemLike,-    StmtLike, Type, Value,+    prepend_stmts, prop_name_eq, prop_name_from_ident, ExprCtx, ExprExt, ExprFactory, IsEmpty,+    ModuleItemLike, StmtLike, Type, Value, }; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith}; #[cfg(all(debug_assertions, feature = "debug"))]@@ -80,7 +80,6 @@ pub(super) fn optimizer<'a>(             remaining_depth: 6,         },         scope: marks.top_level_ctxt,-        var_scope: marks.top_level_ctxt,         bit_ctx: BitCtx::default(),     }; @@ -112,9 +111,6 @@ struct Ctx {     /// Current scope.     scope: SyntaxContext, -    /// fn or top level scope-    var_scope: SyntaxContext,-     bit_ctx: BitCtx, } @@ -196,6 +192,17 @@ bitflags! {          /// `true` while we are inside a class body.         const InClass = 1 << 27;++        /// `true` only while visiting the callee of a `/*#__NOINLINE__*/` call.+        const IsNoInlineCallee = 1 << 28;++        /// `true` only while visiting [CallExpr::callee] when the call result is+        /// not itself used as a receiver-aware callee.+        const IsCallCallee = 1 << 29;++        /// In try or catch block with finally, return may not be the actual last+        /// statement of function+        const InTryCatchWithFinally = 1 << 30;     } } @@ -348,7 +355,7 @@ impl From<&Function> for FnMetadata {             len: f                 .params                 .iter()-                .filter(|p| matches!(&p.pat, Pat::Ident(..) | Pat::Array(..) | Pat::Object(..)))+                .take_while(|p| !matches!(&p.pat, Pat::Assign(..) | Pat::Rest(..)))                 .count(),         }     }@@ -392,7 +399,7 @@ impl Optimizer<'_> {          if self             .data-            .get_scope(self.ctx.var_scope)+            .get_scope(self.ctx.scope)             .unwrap()             .contains(ScopeData::HAS_EVAL_CALL)         {@@ -578,8 +585,11 @@ impl Optimizer<'_> {     ///     /// - `undefined` => `void 0`     fn compress_undefined(&mut self, e: &mut Expr) {-        if let Expr::Ident(Ident { span, sym, .. }) = e {-            if &**sym == "undefined" {+        if let Expr::Ident(Ident {+            span, sym, ctxt, ..+        }) = e+        {+            if &**sym == "undefined" && *ctxt == self.ctx.expr_ctx.unresolved_ctxt {                 *e = *Expr::undefined(*span);             }         }@@ -638,6 +648,40 @@ impl Optimizer<'_> {         )     } +    /// Returns true if invoking an empty-body IIFE without arguments cannot+    /// have observable parameter-initialization effects.+    ///+    /// Destructuring and non-identifier rest parameters are deliberately+    /// rejected. They can throw while binding omitted arguments, and modeling+    /// every safe pattern here would make this otherwise local optimization+    /// unsound. A simple rest binding only initializes an empty array.+    fn can_drop_empty_iife(&self, callee: &Expr) -> bool {+        match callee {+            Expr::Fn(f) if f.function.body.is_empty() => f+                .function+                .params+                .iter()+                .all(|param| self.is_empty_iife_param_safe(&param.pat)),+            Expr::Arrow(f) if matches!(&*f.body, ArrowFunctionBody::FunctionBody(body) if body.stmts.is_empty()) => {+                f.params+                    .iter()+                    .all(|param| self.is_empty_iife_param_safe(param))+            }+            _ => false,+        }+    }++    fn is_empty_iife_param_safe(&self, pat: &Pat) -> bool {+        match pat {+            Pat::Ident(..) => true,+            Pat::Rest(rest) if rest.arg.is_ident() => true,+            Pat::Assign(assign) if assign.left.is_ident() => {+                !assign.right.may_have_side_effects(self.ctx.expr_ctx)+            }+            _ => false,+        }+    }+     /// Returns [None] if expression is side-effect-free.     /// If an expression has a side effect, only side effects are returned.     #[cfg_attr(@@ -807,22 +851,7 @@ impl Optimizer<'_> {                 callee: Callee::Expr(callee),                 args,                 ..-            }) if match &**callee {-                Expr::Fn(f) => f-                    .function-                    .body-                    .as_ref()-                    .map(|body| body.stmts.is_empty())-                    .unwrap_or(false),-                Expr::Arrow(f) => match &*f.body {-                    ArrowFunctionBody::FunctionBody(body) => body.stmts.is_empty(),-                    ArrowFunctionBody::Expr(_) => false,-                    #[cfg(swc_ast_unknown)]-                    _ => panic!("unable to access unknown nodes"),-                },-                _ => false,-            } && args.is_empty() =>-            {+            }) if args.is_empty() && self.can_drop_empty_iife(callee) => {                 report_change!("ignore_return_value: Dropping a pure call");                 self.changed = true;                 return None;@@ -847,12 +876,8 @@ impl Optimizer<'_> {                     }                 } -                if args.is_empty() {-                    if let Expr::Fn(f) = &mut **callee {-                        if f.function.body.is_empty() {-                            return None;-                        }-                    }+                if args.is_empty() && self.can_drop_empty_iife(callee) {+                    return None;                 }                  if let Expr::Ident(callee) = &**callee {@@ -1483,10 +1508,11 @@ impl Optimizer<'_> {                 .ctx                 .bit_ctx                 .with(BitCtx::InFnLike, true)+                // The outer try/finally cannot observe termination within a nested function.+                .with(BitCtx::InTryCatchWithFinally, false)                 .with(BitCtx::TopLevel, false)                 .with(BitCtx::InParam, false),             scope,-            var_scope: scope,             ..self.ctx.clone()         }     }@@ -1658,6 +1684,16 @@ impl VisitMut for Optimizer<'_> {         }     } +    /// Preserve the unbound-call form when an optional call's callee is itself+    /// a call.+    fn visit_mut_opt_call(&mut self, n: &mut OptCall) {+        n.callee.visit_mut_with(+            &mut *self.with_ctx(self.ctx.clone().with(BitCtx::IsThisAwareCallee, true)),+        );++        n.args.visit_mut_with(self);+    }+     #[cfg_attr(         all(debug_assertions, feature = "debug"),         tracing::instrument(level = "debug", skip_all)@@ -1678,11 +1714,19 @@ impl VisitMut for Optimizer<'_> {             #[cfg(swc_ast_unknown)]             _ => panic!("unable to access unknown nodes"),         };+        let is_noinline = self.has_noinline(e.ctxt);+        // If this call produces the value for another receiver-aware callee, such+        // as a template tag, keep the inner callee as a reference. Replacing its+        // member expression can expose the returned callable to later callee+        // rewrites and lose the required `this` form.+        let can_replace_callee = !self.ctx.bit_ctx.contains(BitCtx::IsThisAwareCallee);         {             let ctx = self                 .ctx                 .clone()                 .with(BitCtx::IsCallee, true)+                .with(BitCtx::IsCallCallee, can_replace_callee)+                .with(BitCtx::IsNoInlineCallee, is_noinline)                 .with(                     BitCtx::IsThisAwareCallee,                     is_this_undefined@@ -1822,7 +1866,6 @@ impl VisitMut for Optimizer<'_> {                         .with(BitCtx::InBlock, true)                         .with(BitCtx::InParam, false),                     scope: s.body.ctxt,-                    var_scope: s.body.ctxt,                     ..self.ctx.clone()                 };                 n.visit_mut_children_with(&mut *self.with_ctx(ctx));@@ -3065,10 +3108,17 @@ impl VisitMut for Optimizer<'_> {         tracing::instrument(level = "debug", skip_all)     )]     fn visit_mut_try_stmt(&mut self, n: &mut TryStmt) {-        let ctx = self.ctx.clone().with(BitCtx::InTryBlock, true);-        n.block.visit_mut_with(&mut *self.with_ctx(ctx));+        let ctx = self.ctx.clone().with(+            BitCtx::InTryCatchWithFinally,+            n.finalizer.is_some() || self.ctx.bit_ctx.contains(BitCtx::InTryCatchWithFinally),+        ); -        n.handler.visit_mut_with(self);+        {+            let ctx = self.ctx.clone().with(BitCtx::InTryBlock, true);+            n.block.visit_mut_with(&mut *self.with_ctx(ctx.clone()));+        }++        n.handler.visit_mut_with(&mut *self.with_ctx(ctx));          n.finalizer.visit_mut_with(self);     }@@ -3342,9 +3392,13 @@ impl VisitMut for Optimizer<'_> {                     continue;                 } -                // If initializer is none, we can check next item without thinking about side-                // effects.                 if v.init.is_none() {+                    // Even without an initializer, `let` ends the binding's TDZ here.+                    // Later initializers may assign to it, so their side effects must+                    // not be moved before the declaration. `var` is already initialized.+                    if self.ctx.bit_ctx.contains(BitCtx::IsLet) {+                        can_prepend = false;+                    }                     continue;                 } @@ -3480,7 +3534,26 @@ fn is_callee_this_aware(callee: &Expr) -> bool {     match callee {         Expr::Arrow(..) => return false,         Expr::Seq(..) => return true,-        Expr::Member(MemberExpr { obj, .. }) => {+        Expr::Member(MemberExpr { obj, prop, .. }) => {+            // An object-literal arrow property has no receiver-sensitive `this`.+            // Treating it like a normal member would unnecessarily block safe+            // call reduction after property inlining.+            if let (Expr::Object(obj), MemberProp::Ident(prop)) = (&**obj, prop) {+                let mut matching_props = obj.props.iter().filter(|item| match item {+                    PropOrSpread::Prop(item) => match &**item {+                        Prop::KeyValue(item) => prop_name_eq(&item.key, &prop.sym),+                        _ => false,+                    },+                    _ => false,+                });+                if let Some(PropOrSpread::Prop(item)) = matching_props.next() {+                    if matching_props.next().is_none()+                        && matches!(&**item, Prop::KeyValue(item) if matches!(&*item.value, Expr::Arrow(..)))+                    {+                        return false;+                    }+                }+            }             if let Expr::Ident(obj) = &**obj {                 if &*obj.sym == "console" {                     return false;

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/ops.rs

@@ -42,7 +42,7 @@ impl Optimizer<'_> {         if e.op == op!("===") || e.op == op!("!==") {             if (e.left.is_ident() || e.left.is_member())                 && e.left.eq_ignore_span(&e.right)-                && !contains_update_or_assign(&e.left)+                && is_stable_for_repeated_evaluation(&e.left)             {                 self.changed = true;                 report_change!("Reducing comparison of same variable ({})", e.op);@@ -304,72 +304,31 @@ impl Optimizer<'_> {     } } -/// Check if an expression contains update expressions (++, --) or assignments-/// that would make duplicate evaluations produce different results.-fn contains_update_or_assign(expr: &Expr) -> bool {+/// Check whether evaluating an expression again is safe for the structural+/// equality optimization.+///+/// Calls can produce a different value on each evaluation even if neither the+/// callee nor its arguments contain an update or assignment. Restrict this to+/// stable identifier and member accesses instead of attempting to enumerate+/// every expression that may change between evaluations.+fn is_stable_for_repeated_evaluation(expr: &Expr) -> bool {     match expr {-        Expr::Update(..) | Expr::Assign(..) => true,--        Expr::Bin(BinExpr { left, right, .. }) => {-            contains_update_or_assign(left) || contains_update_or_assign(right)-        }--        Expr::Unary(UnaryExpr { arg, .. }) => contains_update_or_assign(arg),--        Expr::Cond(CondExpr {-            test, cons, alt, ..-        }) => {-            contains_update_or_assign(test)-                || contains_update_or_assign(cons)-                || contains_update_or_assign(alt)-        }+        Expr::Ident(..)+        | Expr::This(..)+        | Expr::Lit(+            Lit::Str(..) | Lit::Bool(..) | Lit::Null(..) | Lit::Num(..) | Lit::BigInt(..),+        ) => true,          Expr::Member(MemberExpr { obj, prop, .. }) => {-            contains_update_or_assign(obj)-                || match prop {+            is_stable_for_repeated_evaluation(obj)+                && match prop {                     MemberProp::Computed(ComputedPropName { expr, .. }) => {-                        contains_update_or_assign(expr)+                        is_stable_for_repeated_evaluation(expr)                     }-                    _ => false,+                    _ => true,                 }         } -        Expr::Call(CallExpr {-            callee: Callee::Expr(callee),-            args,-            ..-        }) => {-            contains_update_or_assign(callee)-                || args.iter().any(|arg| contains_update_or_assign(&arg.expr))-        }--        Expr::Seq(SeqExpr { exprs, .. }) => {-            exprs.iter().any(|expr| contains_update_or_assign(expr))-        }--        Expr::Paren(ParenExpr { expr, .. }) => contains_update_or_assign(expr),--        Expr::OptChain(OptChainExpr { base, .. }) => match &**base {-            OptChainBase::Member(member) => {-                contains_update_or_assign(&member.obj)-                    || match &member.prop {-                        MemberProp::Computed(ComputedPropName { expr, .. }) => {-                            contains_update_or_assign(expr)-                        }-                        _ => false,-                    }-            }-            OptChainBase::Call(call) => {-                contains_update_or_assign(&call.callee)-                    || call-                        .args-                        .iter()-                        .any(|arg| contains_update_or_assign(&arg.expr))-            }-            #[cfg(swc_ast_unknown)]-            _ => false,-        },-         _ => false,     } }

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/props.rs

@@ -2,6 +2,7 @@ use std::borrow::Borrow;  use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*;+use swc_ecma_transforms_base::rename::contains_eval; use swc_ecma_utils::{contains_this_expr, private_ident, prop_name_eq, ExprExt};  use super::{unused::PropertyAccessOpts, BitCtx, Optimizer};@@ -43,7 +44,8 @@ impl Optimizer<'_> {             let usage = self.data.vars.get(&name.to_id())?;             if usage.mutated()                 || usage.flags.intersects(-                    VarUsageInfoFlags::USED_ABOVE_DECL+                    VarUsageInfoFlags::INLINE_PREVENTED+                        .union(VarUsageInfoFlags::USED_ABOVE_DECL)                         .union(VarUsageInfoFlags::USED_AS_REF)                         .union(VarUsageInfoFlags::USED_AS_ARG)                         .union(VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY)@@ -54,6 +56,12 @@ impl Optimizer<'_> {                 return None;             } +            // `callee_count` is tracked for the object, not for each property. Once+            // properties are hoisted, all replacements lose the original object+            // receiver, so every property must be safe in callee position if any+            // property is called.+            let may_be_callee = usage.callee_count != 0;+             if usage.accessed_props.is_empty() {                 log_abort!(                     "hoist_props: Variable `{}` is not accessed with known keys",@@ -90,7 +98,7 @@ impl Optimizer<'_> {                      match &**prop {                         Prop::KeyValue(p) => {-                            if !is_expr_fine_for_hoist_props(&p.value) {+                            if !is_expr_fine_for_hoist_props(&p.value, may_be_callee) {                                 return None;                             } @@ -109,6 +117,10 @@ impl Optimizer<'_> {                             }                         }                         Prop::Shorthand(p) => {+                            if may_be_callee {+                                return None;+                            }+                             if let Some(v) = unknown_used_props.get_mut(p.sym.borrow()) {                                 *v = 0;                             }@@ -233,19 +245,34 @@ impl Optimizer<'_> {     } } -fn is_expr_fine_for_hoist_props(value: &Expr) -> bool {+fn is_expr_fine_for_hoist_props(value: &Expr, may_be_callee: bool) -> bool {     match value {-        Expr::Ident(..) | Expr::Lit(..) | Expr::Arrow(..) | Expr::Class(..) => true,+        // We do not track fixed values for every identifier, so detaching an+        // identifier from an object may change the `this` received by the callee.+        Expr::Ident(..) => !may_be_callee,++        Expr::Lit(..) | Expr::Class(..) => true, -        Expr::Fn(f) => !contains_this_expr(&f.function.body),+        // Arrows keep their lexical `this` when detached from an object.+        Expr::Arrow(..) => true, +        Expr::Fn(f) => {+            // Parameter initializers run with the call receiver too, so checking+            // only the body can miss receiver-sensitive default values.+            !contains_this_expr(&f.function.params)+                && !contains_this_expr(&f.function.body)+                && (!may_be_callee || !contains_eval(&f.function, false))+        }++        // Expressions nested in these containers cannot receive the original+        // object's receiver, so do not propagate `may_be_callee` into them.         Expr::Unary(u) => match u.op {-            op!("void") | op!("typeof") | op!("!") => is_expr_fine_for_hoist_props(&u.arg),+            op!("void") | op!("typeof") | op!("!") => is_expr_fine_for_hoist_props(&u.arg, false),             _ => false,         },          Expr::Array(a) => a.elems.iter().all(|elem| match elem {-            Some(elem) => elem.spread.is_none() && is_expr_fine_for_hoist_props(&elem.expr),+            Some(elem) => elem.spread.is_none() && is_expr_fine_for_hoist_props(&elem.expr, false),             None => true,         }), @@ -253,7 +280,7 @@ fn is_expr_fine_for_hoist_props(value: &Expr) -> bool {             PropOrSpread::Spread(_) => false,             PropOrSpread::Prop(p) => match &**p {                 Prop::Shorthand(..) => true,-                Prop::KeyValue(p) => is_expr_fine_for_hoist_props(&p.value),+                Prop::KeyValue(p) => is_expr_fine_for_hoist_props(&p.value, false),                 _ => false,             },             #[cfg(swc_ast_unknown)]@@ -274,7 +301,11 @@ impl Optimizer<'_> {         if self             .ctx             .bit_ctx-            .intersects(BitCtx::IsUpdateArg | BitCtx::IsCallee | BitCtx::IsExactLhsOfAssign)+            .intersects(BitCtx::IsUpdateArg | BitCtx::IsExactLhsOfAssign)+            || self.ctx.bit_ctx.contains(BitCtx::IsCallee)+                && (!self.options.hoist_props+                    || !self.ctx.bit_ctx.contains(BitCtx::IsCallCallee)+                    || self.ctx.bit_ctx.contains(BitCtx::IsNoInlineCallee))         {             return;         }@@ -350,13 +381,30 @@ impl Optimizer<'_> {             return;         } -        for prop in &obj.props {+        for (idx, prop) in obj.props.iter().enumerate() {             match prop {                 PropOrSpread::Spread(_) => {}                 PropOrSpread::Prop(p) => match &**p {                     Prop::Shorthand(_) => {}                     Prop::KeyValue(p) => {                         if prop_name_eq(&p.key, &key.sym) {+                            if self.ctx.bit_ctx.contains(BitCtx::IsCallee)+                                && !is_expr_fine_for_hoist_props(&p.value, true)+                            {+                                return;+                            }++                            // A later spread can replace this property even if reading the+                            // spread itself has no observable side effect. Do not detach the+                            // earlier callee unless it remains the property's final value.+                            if self.ctx.bit_ctx.contains(BitCtx::IsCallee)+                                && obj.props[idx + 1..]+                                    .iter()+                                    .any(|prop| matches!(prop, PropOrSpread::Spread(_)))+                            {+                                return;+                            }+                             report_change!(                                 "properties: Inlining a key-value property `{}`",                                 key.sym

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs

@@ -1,6 +1,7 @@ use swc_ecma_ast::*;  use super::Optimizer;+use crate::program_data::ScopeData;  /// Methods related to rest parameter optimization. impl Optimizer<'_> {@@ -49,6 +50,12 @@ impl Optimizer<'_> {         if let Some(usage) = self.data.vars.get(&rest_id) {             // If the parameter is not referenced, we can remove it             if usage.ref_count == 0 {+                if let Some(scope) = self.data.get_scope(f.ctxt) {+                    if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {+                        return;+                    }+                }+                 self.changed = true;                 report_change!("rest_params: Removing unused rest parameter");                 f.params.pop();

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs

@@ -883,11 +883,13 @@ impl Optimizer<'_> {                         }                         Mergable::FnDecl(..) => continue,                         Mergable::Drop => {-                            if self.drop_mergable_seq(a)? {-                                changed = true;-                                merge_seq_cache.invalidate(a_idx);-                                merge_seq_cache.invalidate(b_idx);-                                break;+                            if let Mergable::Expr(a) = a {+                                if self.optimize_last_expr_before_termination(a) {+                                    changed = true;+                                    merge_seq_cache.invalidate(a_idx);+                                    merge_seq_cache.invalidate(b_idx);+                                    break;+                                }                             }                         }                     }@@ -1042,16 +1044,6 @@ impl Optimizer<'_> {         }     } -    fn drop_mergable_seq(&mut self, a: &mut Mergable) -> Result<bool, ()> {-        if let Mergable::Expr(a) = a {-            if self.optimize_last_expr_before_termination(a) {-                return Ok(true);-            }-        }--        Ok(false)-    }-     fn is_simple_assign_target_skippable_for_seq(         &self,         a: Option<&Mergable>,@@ -1288,6 +1280,12 @@ impl Optimizer<'_> {              Expr::Array(e) => {                 for elem in e.elems.iter().flatten() {+                    // Spreading reads the contents of the iterable, which `a` may have modified.+                    if elem.spread.is_some() {+                        log_abort!("array spread");+                        return false;+                    }+                     if !self.is_skippable_for_seq(a, &elem.expr) {                         log_abort!("array element");                         return false;@@ -1351,7 +1349,7 @@ impl Optimizer<'_> {              Expr::Update(..) => false,             Expr::SuperProp(..) => false,-            Expr::Class(_) => e.may_have_side_effects(self.ctx.expr_ctx),+            Expr::Class(_) => !e.may_have_side_effects(self.ctx.expr_ctx),              Expr::Paren(e) => self.is_skippable_for_seq(a, &e.expr),             Expr::Unary(e) => self.is_skippable_for_seq(a, &e.arg),@@ -1813,6 +1811,11 @@ impl Optimizer<'_> {                         return Ok(true);                     } +                    // Spreading reads the contents of the iterable, which `a` may have modified.+                    if elem.spread.is_some() {+                        break;+                    }+                     if !self.is_skippable_for_seq(Some(a), &elem.expr) {                         // To preserve side-effects, we need to abort.                         break;@@ -1874,6 +1877,11 @@ impl Optimizer<'_> {                         return Ok(true);                     } +                    // Spreading reads the contents of the iterable, which `a` may have modified.+                    if arg.spread.is_some() {+                        return Ok(false);+                    }+                     if !self.is_skippable_for_seq(Some(a), &arg.expr) {                         return Ok(false);                     }@@ -1898,6 +1906,11 @@ impl Optimizer<'_> {                         return Ok(true);                     } +                    // Spreading reads the contents of the iterable, which `a` may have modified.+                    if arg.spread.is_some() {+                        return Ok(false);+                    }+                     if !self.is_skippable_for_seq(Some(a), &arg.expr) {                         return Ok(false);                     }@@ -1928,6 +1941,12 @@ impl Optimizer<'_> {                             return Ok(true);                         } +                        // Spreading reads the contents of the iterable, which `a` may have+                        // modified.+                        if arg.spread.is_some() {+                            return Ok(false);+                        }+                         if !self.is_skippable_for_seq(Some(a), &arg.expr) {                             return Ok(false);                         }

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/strings.rs

@@ -228,8 +228,8 @@ pub(super) fn convert_str_value_to_tpl_raw(value: &Wtf8Atom) -> Atom {                 _ => result.push(ch),             }         } else {-            // Unparied surrogate, escape as \\uXXXX (two backslashes)-            result.push_str(&format!("\\\\u{:04X}", code_point.to_u32()));+            // Unpaired surrogates must remain Unicode escapes in a template raw value.+            result.push_str(&format!("\\u{:04X}", code_point.to_u32()));         }     } 

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unsafes.rs

@@ -13,14 +13,26 @@ impl Optimizer<'_> {         match &e.callee {             Callee::Super(_) | Callee::Import(_) => return,             Callee::Expr(callee) => match &**callee {-                Expr::Ident(Ident { sym, .. }) if &**sym == "Symbol" => {}+                Expr::Ident(Ident { sym, ctxt, .. })+                    if &**sym == "Symbol" && *ctxt == self.ctx.expr_ctx.unresolved_ctxt => {}                 _ => return,             },             #[cfg(swc_ast_unknown)]             _ => panic!("unable to access unknown nodes"),         } -        e.args-            .retain(|arg| arg.expr.may_have_side_effects(self.ctx.expr_ctx));+        let preserve_first_arg = e+            .args+            .iter()+            .position(|arg| arg.spread.is_none())+            .filter(|&index| e.args[index + 1..].iter().any(|arg| arg.spread.is_some()));+        let mut index = 0;+        e.args.retain(|arg| {+            let should_retain = (Some(index) == preserve_first_arg)+                || arg.spread.is_some()+                || arg.expr.may_have_side_effects(self.ctx.expr_ctx);+            index += 1;+            should_retain+        });     } }

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unused.rs

@@ -4,7 +4,9 @@ use rustc_hash::FxHashSet; use swc_atoms::Atom; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*;-use swc_ecma_utils::{contains_ident_ref, contains_this_expr, find_pat_ids, ExprExt, Value};+use swc_ecma_utils::{+    contains_ident_ref, contains_this_expr, find_pat_ids, ExprExt, IsEmpty, Value,+}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};  use super::Optimizer;@@ -270,6 +272,69 @@ impl Optimizer<'_> {         }     } +    /// Drops an unused shorthand object-pattern binding only when its default+    /// can be discarded.+    #[cfg_attr(+        all(debug_assertions, feature = "debug"),+        tracing::instrument(level = "debug", skip_all)+    )]+    fn take_assign_pat_prop_if_unused(&mut self, key: &mut Ident, value: &mut Expr) {+        trace_op!("unused: Checking object pattern identifier `{}`", key);++        if !self.may_remove_ident(key) {+            log_abort!(+                "unused: Preserving object pattern var `{:#?}` because it's top-level",+                key+            );+            return;+        }++        if let Some(v) = self.data.vars.get(&key.to_id()) {+            let is_used_in_member =+                v.property_mutation_count > 0 || v.flags.contains(VarUsageInfoFlags::USED_AS_REF);+            if v.ref_count != 0+                || v.usage_count != 0+                || v.flags.contains(VarUsageInfoFlags::REASSIGNED)+                || is_used_in_member+            {+                log_abort!(+                    "unused: Cannot drop object pattern binding ({}) because it's used",+                    dump(&*key, false)+                );+                return;+            }++            // Some expressions have observable evaluation that+            // `ignore_return_value` cannot preserve. Do not pass those defaults+            // to it, because a destructuring default is evaluated when the+            // property value is `undefined`.+            let mut visitor = NonDiscardableDefaultVisitor::default();+            value.visit_with(&mut visitor);+            if visitor.found {+                log_abort!("unused: Preserving object pattern default with observable evaluation");+                return;+            }++            // A destructuring default is evaluated only when the property access produces+            // `undefined`, so any remaining effects must stay in this default expression.+            // The enclosing pattern has already proven the property access itself+            // discardable.+            if let Some(side_effects) = self.ignore_return_value(value) {+                *value = side_effects;+                return;+            }++            self.changed = true;+            report_change!(+                "unused: Dropping object pattern binding '{}{:?}' because it and its default are \+                 unused",+                key.sym,+                key.ctxt+            );+            key.take();+        }+    }+     pub(crate) fn should_preserve_property_access(         &self,         e: &Expr,@@ -321,10 +386,8 @@ impl Optimizer<'_> {                         Prop::KeyValue(k) => {                             // Check if `k` is __proto__ -                            if let PropName::Ident(i) = &k.key {-                                if i.sym == "__proto__" {-                                    return true;-                                }+                            if is_proto_key(&k.key) {+                                return true;                             }                              false@@ -377,13 +440,19 @@ impl Optimizer<'_> {          trace_op!("unused: take_pat_if_unused({})", dump(&*name, false)); -        let pure_mark = self.marks.pure;-        let has_pure_ann = match init {-            Some(Expr::Call(c)) => c.ctxt.has_mark(pure_mark),-            Some(Expr::New(n)) => n.ctxt.has_mark(pure_mark),-            Some(Expr::TaggedTpl(t)) => t.ctxt.has_mark(pure_mark),-            _ => false,-        };+        // A PURE annotation on the initializer does not make destructuring+        // safe to remove: the pattern can still invoke getters or throw on a+        // nullish value. Keep the fix from #12386 when reverting #12384.+        // Restrict this to structural values known to be non-nullish so removing+        // the whole pattern cannot remove a destructuring error. A pure annotation+        // only describes evaluation effects, not the value produced by the+        // initializer. Classes are excluded because accessing a static property can+        // invoke a user-defined static getter.+        let can_drop_object_assign_default = init+            .as_deref()+            .is_some_and(is_safe_object_assign_default_initializer);+        let is_function_object_assign_default =+            matches!(init.as_deref(), Some(Expr::Fn(..) | Expr::Arrow(..)));          if !name.is_ident() {             // For Pat::Assign (default parameters), we can skip the pure_getters check@@ -393,23 +462,21 @@ impl Optimizer<'_> {              if !is_assign_pat_without_init {                 // TODO: Use smart logic-                if self.options.pure_getters != PureGetterOption::Bool(true) && !has_pure_ann {+                if self.options.pure_getters != PureGetterOption::Bool(true) {                     return;                 } -                if !has_pure_ann {-                    if let Some(init) = init.as_mut() {-                        if !matches!(init, Expr::Ident(_))-                            && self.should_preserve_property_access(-                                init,-                                PropertyAccessOpts {-                                    allow_getter: false,-                                    only_ident: false,-                                },-                            )-                        {-                            return;-                        }+                if let Some(init) = init.as_mut() {+                    if !matches!(init, Expr::Ident(_))+                        && self.should_preserve_property_access(+                            init,+                            PropertyAccessOpts {+                                allow_getter: false,+                                only_ident: false,+                            },+                        )+                    {+                        return;                     }                 }             }@@ -439,10 +506,6 @@ impl Optimizer<'_> {                         }                     }                 }--                if has_pure_ann && arr.elems.iter().all(|e| e.is_none()) {-                    name.take();-                }             }              Pat::Object(obj) => {@@ -465,13 +528,17 @@ impl Optimizer<'_> {                             self.take_pat_if_unused(&mut p.value, None, is_var_decl);                         }                         ObjectPatProp::Assign(AssignPatProp { key, value, .. }) => {-                            if has_pure_ann {-                                if let Some(e) = value {-                                    *value = self.ignore_return_value(e).map(Box::new);+                            if let Some(value) = value {+                                // Accessing `arguments` on a function throws. Unlike+                                // `caller`, this behavior is not covered by the minifier's+                                // documented assumptions.+                                if can_drop_object_assign_default+                                    && !(is_function_object_assign_default+                                        && key.sym == "arguments")+                                {+                                    self.take_assign_pat_prop_if_unused(key, value.as_mut());                                 }-                            }--                            if value.is_none() {+                            } else {                                 self.take_ident_of_pat_if_unused(key, None);                             }                         }@@ -705,15 +772,6 @@ impl Optimizer<'_> {             return;         } -        if self-            .data-            .get_scope(self.ctx.var_scope)-            .unwrap()-            .intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT))-        {-            return;-        }-         let assign = match e {             Expr::Assign(AssignExpr { op: op!("="), .. }) => return,             // RHS may not be evaluated@@ -723,6 +781,12 @@ impl Optimizer<'_> {         };          if let AssignTarget::Simple(SimpleAssignTarget::Ident(left)) = &assign.left {+            if let Some(scope) = self.data.get_scope(left.id.ctxt) {+                if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {+                    return;+                }+            }+             if let Some(var) = self.data.vars.get(&left.to_id()) {                 // TODO: We don't need fn_local check                 if var@@ -752,15 +816,6 @@ impl Optimizer<'_> {             return;         } -        if self-            .data-            .get_scope(self.ctx.var_scope)-            .unwrap()-            .intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT))-        {-            return;-        }-         let assign = match e {             Expr::Assign(e) => e,             _ => return,@@ -780,6 +835,12 @@ impl Optimizer<'_> {                 return;             } +            if let Some(scope) = self.data.get_scope(i.ctxt) {+                if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {+                    return;+                }+            }+             if let Some(var) = self.data.vars.get(&i.to_id()) {                 // technically this is inline                 if !var.flags.intersects(@@ -787,7 +848,7 @@ impl Optimizer<'_> {                 ) && var.usage_count == 0                     && var.flags.contains(VarUsageInfoFlags::DECLARED)                     && (!var.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM)-                        || !self.data.used_arguments(self.ctx.scope)+                        || !self.data.used_arguments(i.ctxt)                         || self.ctx.expr_ctx.in_strict)                 {                     report_change!(@@ -933,7 +994,10 @@ impl Optimizer<'_> {                 return;             }; -            if contains_ident_ref(&f.function.body, f_ident) {+            // Default parameter initializers run in the function's scope and can reference+            // the function expression's internal name, so inspect the whole+            // function rather than only its body.+            if self.options.keep_fnames || contains_ident_ref(&f.function, f_ident) {                 return;             } @@ -1362,6 +1426,428 @@ fn can_remove_property(sym: &swc_atoms::Wtf8Atom) -> bool {         .map_or(true, |s| !matches!(s, "toString" | "valueOf")) } +/// Returns true for a function `arguments` access that can throw.+fn is_restricted_function_property_access(member: &MemberExpr) -> bool {+    let is_arguments = match &member.prop {+        MemberProp::Ident(prop) => prop.sym == "arguments",+        MemberProp::Computed(prop) => matches!(+            &*prop.expr,+            Expr::Lit(Lit::Str(prop)) if prop.value.as_str() == Some("arguments")+        ),+        _ => false,+    };++    is_arguments+        && matches!(+            member.obj.unwrap_parens(),+            Expr::Fn(..) | Expr::Arrow(..) | Expr::Class(..)+        )+}++/// Returns true for pure member calls whose argument coercion can throw.+fn is_potentially_throwing_pure_member_call(callee: &Expr, args: &[ExprOrSpread]) -> bool {+    if args.is_empty() {+        return false;+    }++    let Expr::Member(MemberExpr {+        obj,+        prop: MemberProp::Ident(..),+        ..+    }) = callee.unwrap_parens()+    else {+        return false;+    };++    matches!(&**obj, Expr::Lit(Lit::Str(..)))+        || matches!(&**obj, Expr::Tpl(Tpl { exprs, .. }) if exprs.is_empty())+        || matches!(&**obj, Expr::Ident(Ident { sym, .. }) if &**sym == "Math")+}++/// Returns true when evaluating an object literal property cannot perform+/// construction-time property-key coercion or spread enumeration.+fn is_non_computed_object_prop(prop: &PropOrSpread) -> bool {+    match prop {+        PropOrSpread::Spread(..) => false,+        PropOrSpread::Prop(prop) => match &**prop {+            // `ignore_return_value` cannot retain shorthand identifier reads,+            // which can throw for unresolved bindings.+            Prop::Shorthand(..) => false,+            Prop::Assign(..) => true,+            Prop::KeyValue(prop) => !prop.key.is_computed() && !is_proto_key(&prop.key),+            Prop::Getter(prop) => !prop.key.is_computed(),+            Prop::Setter(prop) => !prop.key.is_computed(),+            Prop::Method(prop) => !prop.key.is_computed(),+            #[cfg(swc_ast_unknown)]+            _ => panic!("unable to access unknown nodes"),+        },+        #[cfg(swc_ast_unknown)]+        _ => panic!("unable to access unknown nodes"),+    }+}++/// Returns true when an initializer is both non-nullish and can be reduced by+/// `ignore_return_value` without losing construction-time effects.+fn is_safe_object_assign_default_initializer(init: &Expr) -> bool {+    if !matches!(+        init,+        Expr::Array(..) | Expr::Fn(..) | Expr::Arrow(..) | Expr::Object(..)+    ) {+        return false;+    }++    let mut visitor = NonDiscardableDefaultVisitor::default();+    init.visit_with(&mut visitor);+    !visitor.found+}++/// Returns true for object literal keys with special `__proto__` semantics.+fn is_proto_key(key: &PropName) -> bool {+    match key {+        PropName::Ident(ident) => ident.sym == "__proto__",+        PropName::Str(string) => string.value.as_str() == Some("__proto__"),+        _ => false,+    }+}++/// Returns true when a class member is safely discardable by+/// `extract_class_side_effect`.+fn is_discardable_class_default_member(member: &ClassMember) -> bool {+    match member {+        ClassMember::Method(method) => !method.key.is_computed(),+        ClassMember::ClassProp(prop) => !prop.key.is_computed(),+        ClassMember::AutoAccessor(accessor) => {+            // Static auto-accessor initializers run while evaluating the class,+            // but the class-side-effect extractor cannot reconstruct them.+            (!accessor.is_static || accessor.value.is_none())+                && !matches!(&accessor.key, Key::Public(key) if key.is_computed())+        }+        _ => true,+    }+}++/// Finds expressions whose evaluation cannot be reconstructed by+/// `ignore_return_value`.+#[derive(Default)]+struct NonDiscardableDefaultVisitor {+    found: bool,+    in_class_static_initializer: bool,+}++fn has_observable_param_initialization(pat: &Pat) -> bool {+    match pat {+        Pat::Ident(..) => false,+        Pat::Rest(rest) => has_observable_param_initialization(&rest.arg),+        _ => true,+    }+}++impl Visit for NonDiscardableDefaultVisitor {+    noop_visit_type!();++    fn visit_assign_expr(&mut self, e: &AssignExpr) {+        if self.in_class_static_initializer {+            // Class static initializers run in strict mode. Extracting an assignment+            // into the surrounding script can turn a ReferenceError into a global+            // property creation, so retain the class instead.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_update_expr(&mut self, e: &UpdateExpr) {+        if self.in_class_static_initializer {+            // Like assignments, updates of unresolved names throw in a class's+            // strict-mode static initializer but can create globals when moved.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_unary_expr(&mut self, e: &UnaryExpr) {+        if self.in_class_static_initializer && e.op == op!("delete") {+            // Class static initializers run in strict mode. Moving a deletion into+            // a surrounding sloppy script can turn its TypeError into `false`.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_arrow_expr(&mut self, e: &ArrowExpr) {+        // Arrows capture `new.target` lexically. A class side-effect extraction+        // can relocate an arrow without invoking it, which still makes a+        // captured `new.target` invalid at the relocation site.+        e.visit_children_with(self);+    }++    fn visit_function(&mut self, function: &Function) {+        // Method and parameter decorators run while the class is evaluated, but+        // the function body and parameter patterns run only when it is called.+        function.decorators.visit_with(self);+        for param in &function.params {+            param.decorators.visit_with(self);+        }+    }++    fn visit_decorator(&mut self, _: &Decorator) {+        // `extract_class_side_effect` does not retain class or member decorators.+        self.found = true;+    }++    fn visit_this_expr(&mut self, _: &ThisExpr) {+        // `this` can be uninitialized before `super()` in a derived constructor.+        // The side-effect extractor cannot reconstruct that runtime error, so retain+        // defaults that evaluate it.+        self.found = true;+    }++    fn visit_meta_prop_expr(&mut self, e: &MetaPropExpr) {+        if e.kind == MetaPropKind::NewTarget {+            // `new.target` is valid in class field initialization, but moving it+            // outside the class would produce invalid syntax.+            self.found = true;+        }+    }++    fn visit_bin_expr(&mut self, e: &BinExpr) {+        if matches!(+            e.op,+            op!("in")+                | op!("instanceof")+                | op!("<")+                | op!("<=")+                | op!(">")+                | op!(">=")+                | op!("==")+                | op!("!=")+        ) {+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_object_lit(&mut self, e: &ObjectLit) {+        // Object spread performs observable property enumeration and access,+        // while computed keys perform ToPropertyKey coercion. Neither operation+        // is preserved by side-effect extraction when its operand is pure.+        if e.props+            .iter()+            .any(|prop| !is_non_computed_object_prop(prop))+        {+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_array_lit(&mut self, e: &ArrayLit) {+        // Array spread invokes the iterator protocol, which cannot be preserved+        // by reducing the array to its element expressions.+        if e.elems+            .iter()+            .flatten()+            .any(|element| element.spread.is_some())+        {+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_class(&mut self, e: &Class) {+        // Evaluating `class extends value {}` throws unless `value` is a+        // constructor or `null`. The side-effect extractor intentionally only+        // keeps expressions with direct effects, so retain the class default+        // when its heritage cannot be proven to be `null`.+        if e.super_class+            .as_deref()+            .is_some_and(|super_class| !matches!(super_class, Expr::Lit(Lit::Null(..))))+        {+            self.found = true;+            return;+        }++        // Computed class keys perform ToPropertyKey coercion. The side-effect+        // extractor can omit that coercion when the key expression itself is+        // pure, so retain the whole default instead.+        if e.body+            .iter()+            .any(|member| !is_discardable_class_default_member(member))+        {+            self.found = true;+            return;+        }++        for member in &e.body {+            let initializer = match member {+                ClassMember::ClassProp(prop) if prop.is_static => prop.value.as_deref(),+                ClassMember::PrivateProp(prop) if prop.is_static => prop.value.as_deref(),+                ClassMember::StaticBlock(block) if block.body.stmts.len() == 1 => block+                    .body+                    .stmts+                    .first()+                    .and_then(|stmt| stmt.as_expr())+                    .map(|stmt| &*stmt.expr),+                _ => None,+            };++            if let Some(initializer) = initializer {+                let was_in_class_static_initializer = self.in_class_static_initializer;+                self.in_class_static_initializer = true;+                initializer.visit_with(self);+                self.in_class_static_initializer = was_in_class_static_initializer;++                if self.found {+                    return;+                }+            }+        }++        e.visit_children_with(self);+    }++    fn visit_member_expr(&mut self, e: &MemberExpr) {+        if matches!(e.prop, MemberProp::Computed(..)) {+            // A computed member access performs ToPropertyKey coercion. The class+            // side-effect extractor can discard that coercion when the key itself+            // is pure, so retain the default instead.+            self.found = true;+            return;+        }++        if is_restricted_function_property_access(e) {+            // Function `arguments` is a restricted property. Reading it can throw+            // while a function is evaluated in a class initializer, but class-side+            // effect extraction treats ordinary member reads as discardable.+            self.found = true;+            return;+        }++        if matches!(e.prop, MemberProp::PrivateName(..)) {+            // Private member access performs a brand check that can throw even when+            // evaluating the object expression itself has no observable effects.+            // `extract_class_side_effect` cannot reproduce that check outside the+            // class, so keep the class default intact.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_call_expr(&mut self, e: &CallExpr) {+        if e.args.iter().any(|arg| arg.spread.is_some()) {+            // Spread call arguments invoke the iterator protocol, which cannot be+            // preserved by class-side-effect extraction.+            self.found = true;+            return;+        }++        if let Callee::Expr(callee) = &e.callee {+            if is_potentially_throwing_pure_member_call(callee, &e.args) {+                // `may_have_side_effects` recognizes known string and Math methods as pure,+                // but their arguments may throw while being coerced. Class-side effect+                // extraction cannot preserve that exception, so retain the default.+                self.found = true;+                return;+            }+        }++        if matches!(+            &e.callee,+            Callee::Expr(callee) if matches!(callee.unwrap_parens(), Expr::Ident(Ident { sym, .. }) if &**sym == "eval")+        ) {+            // Direct eval observes the class evaluation environment, which cannot+            // be preserved when class side effects are extracted.+            self.found = true;+            return;+        }++        if let Callee::Expr(callee) = &e.callee {+            if let Expr::Arrow(arrow) = &**callee {+                // Unlike a standalone arrow expression, a directly invoked arrow+                // evaluates its body during class evaluation.+                arrow.body.visit_with(self);+                if self.found {+                    return;+                }+            }+        }++        let has_observable_param_initialization = match &e.callee {+            Callee::Expr(callee) => match &**callee {+                Expr::Fn(FnExpr { function, .. }) => {+                    function.body.is_empty()+                        && function+                            .params+                            .iter()+                            .any(|param| has_observable_param_initialization(&param.pat))+                }+                Expr::Arrow(ArrowExpr { body, params, .. }) => {+                    matches!(+                        &**body,+                        ArrowFunctionBody::FunctionBody(body) if body.stmts.is_empty()+                    ) && params.iter().any(has_observable_param_initialization)+                }+                _ => false,+            },+            _ => false,+        };++        if e.args.is_empty() && has_observable_param_initialization {+            // Calling an empty function still initializes omitted parameters.+            // In particular, defaults can run user code and destructuring can+            // throw, neither of which is preserved by the pure-call shortcut.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_opt_call(&mut self, e: &OptCall) {+        if e.args.iter().any(|arg| arg.spread.is_some()) {+            // Spread optional-call arguments invoke the iterator protocol, which+            // cannot be preserved by class-side-effect extraction.+            self.found = true;+            return;+        }++        if is_potentially_throwing_pure_member_call(&e.callee, &e.args) {+            // Optional calls use the same pure-member shortcut as ordinary calls.+            // Retain the default so argument coercion exceptions are preserved.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }++    fn visit_new_expr(&mut self, e: &NewExpr) {+        if e.args+            .as_ref()+            .is_some_and(|args| args.iter().any(|arg| arg.spread.is_some()))+        {+            // Spread constructor arguments invoke the iterator protocol, which+            // cannot be preserved by class-side-effect extraction.+            self.found = true;+            return;+        }++        e.visit_children_with(self);+    }+}+ #[derive(Default)] struct ThisPropertyVisitor {     properties: FxHashSet<Atom>,

deps/swc/crates/swc_ecma_minifier/src/compress/optimize/util.rs

@@ -10,7 +10,8 @@ use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::{Parallel, ParallelExt}; use swc_ecma_utils::{-    collect_decls, prop_name_from_ident, ExprCtx, ExprExt, IdentUsageFinder, Remapper,+    collect_decls, contains_this_expr, prop_name_from_ident, ExprCtx, ExprExt, IdentUsageFinder,+    Remapper, }; use swc_ecma_visit::{     noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,@@ -19,7 +20,7 @@ use swc_ecma_visit::{ use tracing::debug;  use super::{Ctx, Optimizer};-use crate::HEAVY_TASK_PARALLELS;+use crate::{compress::util::contains_super, HEAVY_TASK_PARALLELS};  #[cfg(test)] mod tests;@@ -186,11 +187,16 @@ pub(crate) fn extract_class_side_effect<'a>(     let mut visitor = ClassEffectVisitor {         found: false,         private_ident: FxHashSet::default(),+        nested_class_depth: 0,     };      for m in &mut c.body {         if let ClassMember::PrivateProp(PrivateProp { key, .. })-        | ClassMember::PrivateMethod(PrivateMethod { key, .. }) = m+        | ClassMember::PrivateMethod(PrivateMethod { key, .. })+        | ClassMember::AutoAccessor(AutoAccessor {+            key: Key::Private(key),+            ..+        }) = m         {             visitor.private_ident.insert(key.name.clone());         }@@ -290,6 +296,7 @@ pub(crate) fn extract_class_side_effect<'a>( struct ClassEffectVisitor {     found: bool,     private_ident: FxHashSet<Atom>,+    nested_class_depth: usize, }  impl Visit for ClassEffectVisitor {@@ -298,14 +305,33 @@ impl Visit for ClassEffectVisitor {     /// Don't recurse into constructor     fn visit_constructor(&mut self, _: &Constructor) {} -    /// Don't recurse into fn-    fn visit_fn_decl(&mut self, _: &FnDecl) {}+    fn visit_function(&mut self, n: &Function) {+        if self.found {+            return;+        } -    /// Don't recurse into fn-    fn visit_fn_expr(&mut self, _: &FnExpr) {}+        // Ordinary functions inherit strict mode from the containing class.+        // Extracting an outer class's static initializer would relocate those+        // functions into the surrounding scope and can change their semantics.+        // Functions inside a nested class stay within that class when the+        // initializer is relocated, so their strictness is preserved.+        if self.nested_class_depth == 0 {+            self.found = true;+            return;+        } -    /// Don't recurse into fn-    fn visit_function(&mut self, _: &Function) {}+        // Function bodies are normally evaluated after the class, but private+        // names resolve in the surrounding class environment. Moving a static+        // initializer containing such a function outside the class would make+        // the emitted private-name reference invalid.+        let mut visitor = PrivateNameUsageVisitor {+            found: false,+            private_ident: self.private_ident.clone(),+        };+        n.visit_children_with(&mut visitor);++        self.found |= visitor.found;+    }      fn visit_this_expr(&mut self, _: &ThisExpr) {         self.found = true;@@ -332,22 +358,72 @@ impl Visit for ClassEffectVisitor {     }      fn visit_class(&mut self, n: &Class) {-        let mut new_set = FxHashSet::default();+        // Private declarations belong to the class body, not its heritage or+        // decorators. Those expressions can still reference an enclosing+        // class's private names even if this class redeclares the same name.+        self.nested_class_depth += 1;+        n.decorators.visit_with(self);+        n.super_class.visit_with(self);++        let mut private_ident = self.private_ident.clone();          for m in &n.body {             if let ClassMember::PrivateProp(PrivateProp { key, .. })-            | ClassMember::PrivateMethod(PrivateMethod { key, .. }) = m+            | ClassMember::PrivateMethod(PrivateMethod { key, .. })+            | ClassMember::AutoAccessor(AutoAccessor {+                key: Key::Private(key),+                ..+            }) = m             {-                new_set.insert(key.name.clone());+                private_ident.remove(&key.name);             }         } -        let old_set = mem::replace(&mut self.private_ident, new_set);-        n.visit_children_with(self);+        let old_set = mem::replace(&mut self.private_ident, private_ident);+        n.body.visit_with(self);+        self.nested_class_depth -= 1;         self.private_ident = old_set;     } } +/// Finds uses of the current class's private names, respecting nested class+/// private-name scopes.+struct PrivateNameUsageVisitor {+    found: bool,+    private_ident: FxHashSet<Atom>,+}++impl Visit for PrivateNameUsageVisitor {+    noop_visit_type!();++    fn visit_private_name(&mut self, n: &PrivateName) {+        self.found |= self.private_ident.contains(&n.name);+    }++    fn visit_class(&mut self, n: &Class) {+        n.decorators.visit_with(self);+        n.super_class.visit_with(self);++        let mut private_ident = self.private_ident.clone();++        for member in &n.body {+            if let ClassMember::PrivateProp(PrivateProp { key, .. })+            | ClassMember::PrivateMethod(PrivateMethod { key, .. })+            | ClassMember::AutoAccessor(AutoAccessor {+                key: Key::Private(key),+                ..+            }) = member+            {+                private_ident.remove(&key.name);+            }+        }++        let old_private_ident = mem::replace(&mut self.private_ident, private_ident);+        n.body.visit_with(self);+        self.private_ident = old_private_ident;+    }+}+ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool {     !matches!(e, Expr::Lit(..) | Expr::Unary(..)) }@@ -1012,3 +1088,7 @@ pub fn get_ids_of_pat(pat: &Pat) -> Vec<Id> {     append(pat, &mut idents);     idents }++pub(crate) fn may_inline_arrow(a: &ArrowExpr) -> bool {+    !(contains_super(&a.body) || contains_this_expr(&a.body))+}

deps/swc/crates/swc_ecma_minifier/src/compress/pure/arrows.rs

@@ -1,5 +1,6 @@ use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*;+use swc_ecma_transforms_base::rename::contains_eval; use swc_ecma_utils::{contains_arguments, contains_this_expr};  use super::Pure;@@ -72,7 +73,12 @@ impl Pure<'_> {             if m.function.is_generator                 || contains_arguments(&m.function.body)                 || contains_super(&m.function.body)-                || m.function.params.iter().any(contains_this_expr)+                || m.function.params.iter().any(|param| {+                    contains_this_expr(param)+                        || contains_arguments(param)+                        || contains_super(param)+                        || contains_eval(param, false)+                })             {                 return;             }

deps/swc/crates/swc_ecma_minifier/src/compress/pure/bools.rs

@@ -415,7 +415,15 @@ impl Pure<'_> {              e if is_pure_undefined(self.expr_ctx, e) => true, -            Expr::Ident(i) => i.ctxt != self.expr_ctx.unresolved_ctxt,+            // Deleting a declared binding is false in sloppy mode. Keep unresolved+            // identifiers untouched because they may refer to a deletable global property.+            Expr::Ident(i) if i.ctxt != self.expr_ctx.unresolved_ctxt => {+                self.changed = true;+                let span = delete.arg.span();+                report_change!("booleans: Compressing `delete` => false");+                *e = make_bool(span, false);+                return;+            }              // NaN             Expr::Bin(BinExpr {@@ -691,20 +699,24 @@ impl Pure<'_> {              (Expr::Ident(..), Expr::Lit(..)) if is_for_rel => false, -            (Expr::Ident(..), Expr::Lit(..))-            | (+            (                 Expr::Ident(..) | Expr::Member(..),                 Expr::Unary(UnaryExpr {                     op: op!("void") | op!("!"),+                    arg,                     ..                 }),             )             | (                 Expr::This(..),                 Expr::Unary(UnaryExpr {-                    op: op!("void"), ..+                    op: op!("void"),+                    arg,+                    ..                 }),-            )+            ) if !arg.may_have_side_effects(self.expr_ctx) => true,++            (Expr::Ident(..), Expr::Lit(..))             | (Expr::Unary(..), Expr::Lit(..))             | (Expr::Tpl(..), Expr::Lit(..)) => true,             _ => false,

deps/swc/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs

@@ -1,7 +1,7 @@ use par_iter::prelude::*; use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP}; use swc_ecma_ast::*;-use swc_ecma_utils::{extract_var_ids, ExprCtx, ExprExt, StmtExt, StmtLike, Value};+use swc_ecma_utils::{extract_var_ids, ExprCtx, ExprExt, StmtExt, StmtLike, Type, Value}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};  use super::Pure;@@ -39,26 +39,15 @@ impl Pure<'_> {                 right,                 ..             }) if match &*left {-                AssignTargetPat::Array(arr) => {-                    arr.elems.is_empty() || arr.elems.iter().all(|v| v.is_none())+                AssignTargetPat::Object(obj) => {+                    obj.props.is_empty()+                        && !matches!(+                            right.get_type(self.expr_ctx),+                            Value::Known(Type::Null | Type::Undefined) | Value::Unknown+                        )                 }                 _ => false,             } =>-            {-                report_change!("Dropping assignment to an empty array pattern");-                self.changed = true;-                *e = *right.take();-            }--            Expr::Assign(AssignExpr {-                op: op!("="),-                left: AssignTarget::Pat(left),-                right,-                ..-            }) if match &*left {-                AssignTargetPat::Object(obj) => obj.props.is_empty(),-                _ => false,-            } =>             {                 report_change!("Dropping assignment to an empty object pattern");                 self.changed = true;@@ -399,12 +388,9 @@ impl Pure<'_> {                     }                     if let Some(h) = t.handler.as_mut() {                         if let Some(stmt) = h.body.stmts.last_mut() {-                            changed |= drop(stmt, last, need_break, ctx);-                        }-                    }-                    if let Some(f) = t.finalizer.as_mut() {-                        if let Some(stmt) = f.stmts.last_mut() {-                            changed |= drop(stmt, last, need_break, ctx);+                            if t.finalizer.is_none() {+                                changed |= drop(stmt, last, need_break, ctx);+                            }                         }                     }                     changed@@ -660,7 +646,12 @@ impl Pure<'_> {                 }             } -            Expr::Unary(..) | Expr::Bin(..) | Expr::Cond(..) => {+            Expr::Unary(..) | Expr::Bin(..) | Expr::Cond(..)+                if !arg.may_have_side_effects(ExprCtx {+                    is_unresolved_ref_safe: true,+                    ..self.expr_ctx+                }) =>+            {                 *e = make_bool(e.span(), true);             } 

deps/swc/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs

@@ -1,5 +1,6 @@ use std::{num::FpCategory, ops::RangeInclusive}; +use num_bigint::BigUint; use radix_fmt::Radix; use swc_atoms::atom; use swc_common::{util::take::Take, Spanned};@@ -675,7 +676,10 @@ impl Pure<'_> {          if &*method.sym == "toExponential" {             if first_arg.is_none() {-                let value = f64_to_exponential(num.value).into();+                let Some(value) = f64_to_exponential(num.value) else {+                    return;+                };+                let value = value.into();                  self.changed = true;                 report_change!(@@ -703,7 +707,11 @@ impl Pure<'_> {                         return;                     }; -                    f64_to_exponential_with_precision(num.value, usize::from(p)).into()+                    let Some(value) = f64_to_exponential_with_precision(num.value, usize::from(p))+                    else {+                        return;+                    };+                    value.into()                 } else {                     // 4. If x is not finite, return Number::toString(x, 10).                     num.value.to_js_string().into()@@ -758,6 +766,13 @@ impl Pure<'_> {     }      pub(super) fn eval_opt_chain(&mut self, e: &mut Expr) {+        // Folding a nested continuation independently loses whether an earlier+        // optional continuation short-circuited. Evaluate the full chain from+        // its root so a required continuation can still preserve its throw.+        if self.ctx.contains(Ctx::IN_OPT_CHAIN) {+            return;+        }+         let opt = match e {             Expr::OptChain(e) => e,             _ => return,@@ -765,8 +780,9 @@ impl Pure<'_> {          match &mut *opt.base {             OptChainBase::Member(MemberExpr { span, obj, .. }) => {-                //-                if is_pure_undefined_or_null(self.expr_ctx, obj) {+                if (opt.optional && is_pure_undefined_or_null(self.expr_ctx, obj))+                    || self.is_opt_chain_short_circuited(obj)+                {                     self.changed = true;                     report_change!(                         "evaluate: Reduced an optional chaining operation because object is \@@ -778,7 +794,9 @@ impl Pure<'_> {             }              OptChainBase::Call(OptCall { span, callee, .. }) => {-                if is_pure_undefined_or_null(self.expr_ctx, callee) {+                if (opt.optional && is_pure_undefined_or_null(self.expr_ctx, callee))+                    || self.is_opt_chain_short_circuited(callee)+                {                     self.changed = true;                     report_change!(                         "evaluate: Reduced a call expression with optional chaining operation \@@ -793,6 +811,24 @@ impl Pure<'_> {         }     } +    /// Returns whether an earlier continuation of an optional chain has+    /// definitely short-circuited to `undefined`.+    fn is_opt_chain_short_circuited(&self, expr: &Expr) -> bool {+        let Expr::OptChain(opt) = expr else {+            return false;+        };++        let base = match &*opt.base {+            OptChainBase::Member(member) => &*member.obj,+            OptChainBase::Call(call) => &*call.callee,+            #[cfg(swc_ast_unknown)]+            _ => return false,+        };++        (opt.optional && is_pure_undefined_or_null(self.expr_ctx, base))+            || self.is_opt_chain_short_circuited(base)+    }+     pub(super) fn eval_trivial_values_in_expr(&mut self, seq: &mut SeqExpr) {         if seq.exprs.len() < 2 {             return;@@ -900,6 +936,12 @@ impl Pure<'_> {             _ => panic!("unable to access unknown nodes"),         }; +        // These methods ignore their arguments, but evaluating an argument can+        // still throw or iterate a spread. Fold only calls without arguments.+        if matches!(&*method, "toLowerCase" | "toUpperCase") && !call.args.is_empty() {+            return;+        }+         let new_val = match &*method {             "toLowerCase" => s.value.to_lowercase(),             "toUpperCase" => s.value.to_uppercase(),@@ -997,9 +1039,10 @@ impl Pure<'_> {                         None => {                             self.changed = true;                             report_change!(-                                "evaluate: Evaluated `codePointAt` of a string literal as `NaN`",+                                "evaluate: Evaluated `codePointAt` of a string literal as \+                                 `undefined`",                             );-                            *e = make_number(e.span(), f64::NAN)+                            *e = *Expr::undefined(call.span)                         }                     }                 }@@ -1019,6 +1062,49 @@ impl Pure<'_> {     } } +/// Returns the exact base-10 digits and decimal exponent of a nonzero positive+/// `f64`.+fn f64_to_decimal_digits(value: f64) -> (String, i32) {+    debug_assert!(value.is_finite() && value > 0.0);++    const FRACTION_MASK: u64 = (1 << 52) - 1;++    let bits = value.to_bits();+    let exponent_bits = ((bits >> 52) & 0x7ff) as i32;+    let fraction = bits & FRACTION_MASK;+    let (significand, binary_exponent) = if exponent_bits == 0 {+        (fraction, -1074)+    } else {+        (fraction | (1 << 52), exponent_bits - 1023 - 52)+    };++    let mut coefficient = BigUint::from(significand);+    let decimal_scale = if binary_exponent >= 0 {+        coefficient <<= binary_exponent as usize;+        0+    } else {+        let mut power = (-binary_exponent) as u32;+        let mut factor = BigUint::from(5_u8);++        while power != 0 {+            if power & 1 != 0 {+                coefficient *= &factor;+            }+            power >>= 1;+            if power != 0 {+                factor = &factor * &factor;+            }+        }++        -binary_exponent+    };++    let digits = coefficient.to_str_radix(10);+    let exponent = digits.len() as i32 - decimal_scale - 1;++    (digits, exponent)+}+ // Code from boa // https://github.com/boa-dev/boa/blob/f8b682085d7fe0bbfcd0333038e93cf2f5aee710/boa_engine/src/builtins/number/mod.rs#L408 fn f64_to_precision(value: f64, precision: usize) -> String {@@ -1048,19 +1134,10 @@ fn f64_to_precision(value: f64, precision: usize) -> String {         e = 0;     // 10     } else {-        // Due to f64 limitations, this part differs a bit from the spec,-        // but has the same effect. It manipulates the string constructed-        // by `format`: digits with an optional dot between two of them.-        m = format!("{x:.100}");--        // a: getting an exponent-        e = flt_str_to_exp(&m);-        // b: getting relevant digits only-        if e < 0 {-            m = m.split_off((1 - e) as usize);-        } else if let Some(n) = m.find('.') {-            m.remove(n);-        }+        // Use the exact decimal expansion of the binary float. A fixed-size decimal+        // intermediate can round through an arbitrary carry chain before the requested+        // precision is applied, which changes the result of `Number#toPrecision`.+        (m, e) = f64_to_decimal_digits(x);         // impl: having exactly `precision` digits in `suffix`         if round_to_precision(&mut m, precision) {             e += 1;@@ -1108,25 +1185,6 @@ fn f64_to_precision(value: f64, precision: usize) -> String {     s + &*m } -fn flt_str_to_exp(flt: &str) -> i32 {-    let mut non_zero_encountered = false;-    let mut dot_encountered = false;-    for (i, c) in flt.chars().enumerate() {-        if c == '.' {-            if non_zero_encountered {-                return (i as i32) - 1;-            }-            dot_encountered = true;-        } else if c != '0' {-            if dot_encountered {-                return 1 - (i as i32);-            }-            non_zero_encountered = true;-        }-    }-    (flt.len() as i32) - 1-}- fn round_to_precision(digits: &mut String, precision: usize) -> bool {     if digits.len() > precision {         let to_round = digits.split_off(precision);@@ -1177,34 +1235,186 @@ fn round_to_precision(digits: &mut String, precision: usize) -> bool {     } } -/// Helper function that formats a float as a ES6-style exponential number-/// string.-fn f64_to_exponential(n: f64) -> String {+/// Formats a finite float as an ECMAScript exponential number string.+///+/// `Number::toExponential` omits the sign of both zero values. For all other+/// values, the omitted-precision form uses the shortest ECMAScript decimal+/// representation rather than Rust's formatter, whose rounding rules differ.+fn f64_to_exponential(n: f64) -> Option<String> {     if !n.is_finite() {-        return n.to_js_string();+        return Some(n.to_js_string());     } -    match n.abs() {-        x if x >= 1.0 || x == 0.0 => format!("{n:e}").replace('e', "e+"),-        _ => format!("{n:e}"),-    }+    decimal_to_exponential(&n.to_js_string(), None) } -/// Helper function that formats a float as a ES6-style exponential number-/// string with a given precision.-// We can't use the same approach as in `f64_to_exponential`-// because in cases like (0.999).toExponential(0) the result will be 1e0.-// Instead we get the index of 'e', and if the next character is not '-'-// we insert the plus sign-fn f64_to_exponential_with_precision(n: f64, prec: usize) -> String {+/// Formats a finite float as an ECMAScript exponential number string with+/// `prec` digits after the decimal point.+///+/// The formatter performs ECMAScript's decimal half-tie rounding: when two+/// candidate decimal integers are equally close, it chooses the larger one.+/// It rounds the exact binary representation instead of using a fixed-decimal+/// intermediate, whose final-place rounding can carry into the target digit.+fn f64_to_exponential_with_precision(n: f64, prec: usize) -> Option<String> {     if !n.is_finite() {-        return n.to_js_string();+        return Some(n.to_js_string());     } -    let mut res = format!("{n:.prec$e}");-    let idx = res.find('e').expect("'e' not found in exponential string");-    if res.as_bytes()[idx + 1] != b'-' {-        res.insert(idx + 1, '+');+    if n == 0.0 {+        return Some(if prec == 0 {+            "0e+0".into()+        } else {+            format!("0.{}e+0", "0".repeat(prec))+        });+    }++    let negative = n.is_sign_negative();+    let (_, mut exponent) = decimal_parts(&n.abs().to_js_string())?;+    let bits = n.abs().to_bits();+    let ieee_mantissa = bits & ((1 << 52) - 1);+    let ieee_exponent = ((bits >> 52) & 0x7ff) as i32;+    let (mantissa, binary_exponent) = if ieee_exponent == 0 {+        (ieee_mantissa, 1 - 1023 - 52)+    } else {+        (ieee_mantissa | (1 << 52), ieee_exponent - 1023 - 52)+    };++    // Compute round(abs(n) * 10^(prec - exponent)) using the exact binary+    // significand. This is the integer `m` from Number::toExponential.+    let decimal_shift = prec as i32 - exponent;+    let binary_shift = binary_exponent + decimal_shift;+    let mut numerator = BigUint::from(mantissa);+    let mut denominator = BigUint::from(1u8);++    if decimal_shift >= 0 {+        numerator *= BigUint::from(5u8).pow(decimal_shift as u32);+    } else {+        denominator *= BigUint::from(5u8).pow(decimal_shift.unsigned_abs());+    }++    if binary_shift >= 0 {+        numerator <<= binary_shift as usize;+    } else {+        denominator <<= binary_shift.unsigned_abs() as usize;+    }++    let mut rounded = &numerator / &denominator;+    let remainder = numerator % denominator.clone();+    if remainder * 2u8 >= denominator {+        rounded += 1u8;+    }++    let mut digits = rounded.to_string();+    let significant_digits = prec + 1;+    if digits.len() == significant_digits + 1 {+        // Rounding 9.99... up changes the exponent and contributes one+        // trailing zero that is not part of the requested precision.+        if !digits.ends_with('0') {+            return None;+        }+        digits.pop();+        exponent += 1;+    }+    if digits.len() != significant_digits {+        return None;+    }++    let mut value = String::with_capacity(digits.len() + 6);+    if negative {+        value.push('-');+    }+    value.push(digits.remove(0));+    if !digits.is_empty() {+        value.push('.');+        value.push_str(&digits);+    }+    value.push('e');+    if exponent >= 0 {+        value.push('+');+    }+    value.push_str(&exponent.to_string());++    Some(value)+}++/// Parses an ECMAScript decimal string into its significant digits and base-10+/// exponent. The returned digits preserve trailing zeroes, which are required+/// by explicit `fractionDigits` formatting.+fn decimal_parts(value: &str) -> Option<(String, i32)> {+    let value = value.strip_prefix('-').unwrap_or(value);+    let (coefficient, exponent) = match value.split_once(['e', 'E']) {+        Some((coefficient, exponent)) => (coefficient, exponent.parse::<i32>().ok()?),+        None => (value, 0),+    };++    let (integer, fraction) = match coefficient.split_once('.') {+        Some((integer, fraction)) => (integer, fraction),+        None => (coefficient, ""),+    };++    if integer.is_empty()+        || !integer.bytes().all(|byte| byte.is_ascii_digit())+        || !fraction.bytes().all(|byte| byte.is_ascii_digit())+    {+        return None;+    }++    let decimal_index = integer.len() as i32;+    let mut digits = String::with_capacity(integer.len() + fraction.len());+    digits.push_str(integer);+    digits.push_str(fraction);++    let first_non_zero = digits.bytes().position(|byte| byte != b'0');+    let Some(first_non_zero) = first_non_zero else {+        return Some((String::new(), 0));+    };++    let exponent = exponent + decimal_index - first_non_zero as i32 - 1;+    Some((digits.split_off(first_non_zero), exponent))+}++/// Converts a decimal string produced by ECMAScript-compatible formatting into+/// the canonical notation required by `Number::toExponential`.+fn decimal_to_exponential(value: &str, fraction_digits: Option<usize>) -> Option<String> {+    let negative = value.starts_with('-');+    let (mut digits, exponent) = decimal_parts(value)?;++    if digits.is_empty() {+        let fraction_digits = fraction_digits.unwrap_or(0);+        return Some(if fraction_digits == 0 {+            "0e+0".into()+        } else {+            format!("0.{}e+0", "0".repeat(fraction_digits))+        });     }-    res++    match fraction_digits {+        Some(fraction_digits) => {+            if digits.len() != fraction_digits + 1 {+                return None;+            }+        }+        None => {+            while digits.ends_with('0') {+                digits.pop();+            }+        }+    }++    let mut result = String::with_capacity(digits.len() + 5);+    if negative {+        result.push('-');+    }+    result.push(digits.remove(0));+    if !digits.is_empty() {+        result.push('.');+        result.push_str(&digits);+    }+    result.push('e');+    if exponent >= 0 {+        result.push('+');+    }+    result.push_str(&exponent.to_string());++    Some(result) }

deps/swc/crates/swc_ecma_minifier/src/compress/pure/if_return.rs

@@ -67,7 +67,7 @@ impl Pure<'_> {             _ => return,         }; -        // If we negate a block, these variables will have narrower scope.+        // If we negate a block, these lexical bindings will have narrower scope.         if stmts[pos_of_if..].iter().any(|s| match s {             Stmt::Decl(Decl::Var(v))                 if matches!(@@ -80,6 +80,7 @@ impl Pure<'_> {             {                 true             }+            Stmt::Decl(Decl::Class(..)) => true,             _ => false,         }) {             return;

deps/swc/crates/swc_ecma_minifier/src/compress/pure/loops.rs

@@ -265,8 +265,10 @@ impl Pure<'_> {                 let val = stmt.test.as_pure_bool(self.expr_ctx);                 if let Value::Known(false) = val {                     // A direct break or continue targets this loop and cannot survive-                    // unwrapping.-                    if should_not_inline_loop_body(&stmt.body, false) {+                    // unwrapping. Even removing a terminal jump along with the loop+                    // can expose its ancestors to unsafe purity or directive+                    // simplifications, including during a later minification.+                    if should_not_inline_loop_body(&stmt.body) {                         return;                     } @@ -308,33 +310,23 @@ fn optimize_loop_body(loop_body: &mut Stmt) {     } } -fn should_not_inline_loop_body(s: &Stmt, allow_break_continue: bool) -> bool {+fn should_not_inline_loop_body(s: &Stmt) -> bool {     match s {-        Stmt::Block(s) => s-            .stmts-            .iter()-            .any(|s| should_not_inline_loop_body(s, allow_break_continue)),+        Stmt::Block(s) => s.stmts.iter().any(should_not_inline_loop_body),          Stmt::If(s) => {-            should_not_inline_loop_body(&s.cons, false)+            should_not_inline_loop_body(&s.cons)                 || s.alt                     .as_deref()-                    .map(|s| should_not_inline_loop_body(s, false))+                    .map(should_not_inline_loop_body)                     .unwrap_or_default()         }         Stmt::Switch(s) => s             .cases             .iter()-            .any(|c| c.cons.iter().any(|s| should_not_inline_loop_body(s, false))),--        Stmt::Continue(ContinueStmt {-            label: Some(..), ..-        })-        | Stmt::Break(BreakStmt {-            label: Some(..), ..-        }) => true,+            .any(|c| c.cons.iter().any(should_not_inline_loop_body)), -        Stmt::Break(..) | Stmt::Continue(..) => !allow_break_continue,+        Stmt::Break(..) | Stmt::Continue(..) => true,          Stmt::Return(..)         | Stmt::Throw(..)

deps/swc/crates/swc_ecma_minifier/src/compress/pure/misc.rs

@@ -61,27 +61,6 @@ fn can_compress_new_regexp(args: Option<&[ExprOrSpread]>) -> bool {     } } -fn may_evaluate_to_nullish(expr_ctx: ExprCtx, expr: &Expr) -> bool {-    if is_pure_undefined(expr_ctx, expr) || matches!(expr, Expr::Lit(Lit::Null(..))) {-        return true;-    }--    match expr {-        Expr::Paren(ParenExpr { expr, .. }) => may_evaluate_to_nullish(expr_ctx, expr),-        Expr::Seq(SeqExpr { exprs, .. }) => match exprs.last() {-            Some(last) => may_evaluate_to_nullish(expr_ctx, last),-            None => false,-        },-        Expr::Cond(CondExpr { cons, alt, .. }) => {-            may_evaluate_to_nullish(expr_ctx, cons) || may_evaluate_to_nullish(expr_ctx, alt)-        }-        _ => matches!(-            expr.get_type(expr_ctx),-            Value::Known(Type::Undefined | Type::Null) | Value::Unknown-        ),-    }-}- fn collect_exprs_from_object(obj: &mut ObjectLit) -> Vec<Box<Expr>> {     let mut exprs = Vec::new(); @@ -123,7 +102,7 @@ fn collect_exprs_from_object(obj: &mut ObjectLit) -> Vec<Box<Expr>> {  #[derive(Debug)] enum GroupType<'a> {-    Literals(Vec<&'a ExprOrSpread>),+    Literals(Vec<Option<&'a ExprOrSpread>>),     Expression(&'a ExprOrSpread), } @@ -786,11 +765,14 @@ impl Pure<'_> {             let mut consecutive_literals = 0;             let mut max_consecutive = 0; -            for elem in elems.iter().flatten() {-                let is_literal = match &*elem.expr {-                    Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,-                    e if is_pure_undefined(self.expr_ctx, e) => true,-                    _ => false,+            for elem in elems.iter() {+                let is_literal = match elem {+                    None => true,+                    Some(elem) => match &*elem.expr {+                        Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,+                        e if is_pure_undefined(self.expr_ctx, e) => true,+                        _ => false,+                    },                 };                  if is_literal {@@ -825,21 +807,26 @@ impl Pure<'_> {         let mut groups = Vec::new();         let mut current_group = Vec::new(); -        for elem in elems.iter().flatten() {-            let is_literal = match &*elem.expr {-                Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,-                e if is_pure_undefined(self.expr_ctx, e) => true,-                _ => false,+        for elem in elems.iter() {+            let is_literal = match elem {+                None => true,+                Some(elem) => match &*elem.expr {+                    Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,+                    e if is_pure_undefined(self.expr_ctx, e) => true,+                    _ => false,+                },             };              if is_literal {-                current_group.push(elem);+                // An elision is an empty join element, so it must stay in the+                // group to preserve its surrounding separator positions.+                current_group.push(elem.as_ref());             } else {                 if !current_group.is_empty() {                     groups.push(GroupType::Literals(current_group));                     current_group = Vec::new();                 }-                groups.push(GroupType::Expression(elem));+                groups.push(GroupType::Expression(elem.as_ref().unwrap()));             }         } @@ -859,9 +846,10 @@ impl Pure<'_> {             if !self.options.unsafe_passes                 && groups.iter().any(|group| match group {                     GroupType::Literals(_) => false,-                    GroupType::Expression(expr) => {-                        may_evaluate_to_nullish(self.expr_ctx, &expr.expr)-                    }+                    GroupType::Expression(expr) => matches!(+                        &expr.expr.get_type(self.expr_ctx,),+                        Value::Known(Type::Null | Type::Undefined) | Value::Unknown+                    ),                 })             {                 return None;@@ -872,30 +860,27 @@ impl Pure<'_> {              // Only add empty string prefix when the first element is a non-string             // expression that needs coercion to string AND there's no string-            // literal early enough to provide coercion+            // literal early enough to provide coercion.             let needs_empty_string_prefix = match groups.first() {                 Some(GroupType::Expression(first_expr)) => {-                    // Check if the first expression is already a string concatenation                     let first_needs_coercion = match &*first_expr.expr {                         Expr::Bin(BinExpr {                             op: op!(bin, "+"), ..-                        }) => false, // Already string concat-                        Expr::Lit(Lit::Str(..)) => false, // Already a string literal-                        Expr::Call(_call) => {-                            // Function calls may return any type and need string coercion-                            true+                        }) => {+                            // `+` is only already a string concatenation when its+                            // result is proven to be a string. Otherwise adjacent+                            // join elements could be added numerically first.+                            first_expr.expr.get_type(self.expr_ctx) != Value::Known(Type::Str)                         }-                        _ => true, // Other expressions need string coercion+                        Expr::Lit(Lit::Str(..)) => false,+                        Expr::Call(..) => true,+                        _ => true,                     }; -                    // If the first element needs coercion, check if the second element is a string-                    // literal that can provide the coercion+                    // A following literal provides the string coercion for the+                    // first element before the next dynamic element is evaluated.                     if first_needs_coercion {-                        match groups.get(1) {-                            Some(GroupType::Literals(_)) => false, /* String literals will */-                            // provide coercion-                            _ => true, // No string literal to provide coercion-                        }+                        !matches!(groups.get(1), Some(GroupType::Literals(_)))                     } else {                         false                     }@@ -916,6 +901,10 @@ impl Pure<'_> {                     GroupType::Literals(literals) => {                         let mut joined = Wtf8Buf::new();                         for literal in literals.iter() {+                            let Some(literal) = literal else {+                                continue;+                            };+                             match &*literal.expr {                                 Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value),                                 Expr::Lit(Lit::Num(n)) => joined.push_str(&n.value.to_js_string()),@@ -972,6 +961,10 @@ impl Pure<'_> {                                 joined.push_wtf8(separator);                             } +                            let Some(literal) = literal else {+                                continue;+                            };+                             match &*literal.expr {                                 Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value),                                 Expr::Lit(Lit::Num(n)) => joined.push_str(&n.value.to_js_string()),@@ -1150,6 +1143,13 @@ impl Pure<'_> {      /// Array() -> []     fn optimize_array(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {+        // Literal array spreads are expanded before this optimization. Any+        // remaining spread has unknown runtime arity, so it may expand to one+        // numeric argument and invoke Array's length-constructor behavior.+        if args.iter().any(|arg| arg.spread.is_some()) {+            return None;+        }+         if args.len() == 1 {             if let ExprOrSpread { spread: None, expr } = &args[0] {                 match &**expr {@@ -1495,7 +1495,7 @@ impl Pure<'_> {         let mut cur_cooked = Wtf8Buf::default();         let mut first = true; -        for elem in elems.take().into_iter().flatten() {+        for elem in elems.take() {             if first {                 first = false;             } else {@@ -1503,6 +1503,10 @@ impl Pure<'_> {                 cur_cooked.push_wtf8(sep);             } +            let Some(elem) = elem else {+                continue;+            };+             match *elem.expr {                 Expr::Tpl(mut tpl) => {                     //@@ -1590,13 +1594,20 @@ impl Pure<'_> {                     false                 }; -                let b = s-                    .finalizer-                    .as_mut()-                    .map(|s| self.drop_return_value(&mut s.stmts))-                    .unwrap_or_default();+                if let Some(s) = &mut s.finalizer {+                    // A finalizer return overrides the pending completion, so only its value can+                    // be removed when the IIFE result is ignored.+                    for stmt in &mut s.stmts {+                        self.ignore_return_value_of_return_stmt(+                            stmt,+                            DropOpts::DROP_GLOBAL_REFS_IF_UNUSED+                                .union(DropOpts::DROP_NUMBER)+                                .union(DropOpts::DROP_STR_LIT),+                        );+                    }+                } -                a || b+                a             }              _ => false,@@ -2210,7 +2221,7 @@ impl Pure<'_> {                 callee: Callee::Expr(callee),                 ..             }) if callee.is_fn_expr() => match &mut **callee {-                Expr::Fn(callee) => {+                Expr::Fn(callee) if !callee.function.is_async => {                     if callee.ident.is_none() {                         if let Some(body) = &mut callee.function.body {                             if self.options.side_effects {@@ -2220,6 +2231,8 @@ impl Pure<'_> {                     }                 } +                Expr::Fn(..) => {}+                 _ => {                     unreachable!()                 }@@ -2235,7 +2248,7 @@ impl Pure<'_> {             }) = e             {                 match &mut **callee {-                    Expr::Fn(callee) => {+                    Expr::Fn(callee) if !callee.function.is_async => {                         if let Some(body) = &mut callee.function.body {                             if let Some(ident) = &callee.ident {                                 if IdentUsageFinder::find(ident, body) {@@ -2248,7 +2261,7 @@ impl Pure<'_> {                             }                         }                     }-                    Expr::Arrow(callee) => match &mut *callee.body {+                    Expr::Arrow(callee) if !callee.is_async => match &mut *callee.body {                         ArrowFunctionBody::FunctionBody(body) => {                             for stmt in &mut body.stmts {                                 self.ignore_return_value_of_return_stmt(stmt, opts);@@ -2272,14 +2285,47 @@ impl Pure<'_> {          if self.options.side_effects && self.options.pristine_globals {             match e {+                // Map and Set synchronously consume their iterable argument. Keeping only the+                // argument expression would skip observable iterator acquisition and iteration.+                // Construction without arguments or with a nullish first argument does not+                // consume an iterable and is still pure.+                Expr::New(NewExpr {+                    span, callee, args, ..+                }) if callee.is_one_of_global_ref_to(self.expr_ctx, &["Map", "Set"])+                    // Spreading consumes an iterator. Extracting only `arg.expr` would create+                    // the iterator but skip its observable iteration, including errors thrown+                    // by the iterator.+                    && !args.iter().flatten().any(|arg| arg.spread.is_some())+                    && args+                        .as_deref()+                        .and_then(|arg| arg.first())+                        .map(|arg| {+                            arg.spread.is_none()+                                && (matches!(+                                    &arg.expr.get_type(self.expr_ctx,),+                                    Value::Known(Type::Null | Type::Undefined)+                                ) || is_valid_map_set_init(&arg.expr, self.expr_ctx, callee))+                        })+                        .unwrap_or(true) =>+                {+                    report_change!("Dropping a pure new expression");++                    self.changed = true;+                    *e = self+                        .make_ignored_expr(+                            *span,+                            args.iter_mut().flatten().map(|arg| arg.expr.take()),+                        )+                        .unwrap_or(Invalid { span: DUMMY_SP }.into());+                    return;+                }+                 Expr::New(NewExpr {                     span, callee, args, ..                 }) if callee.is_one_of_global_ref_to(                     self.expr_ctx,-                    &[-                        "Map", "Set", "Array", "Object", "Boolean", "Number", "String",-                    ],-                ) =>+                    &["Array", "Object", "Boolean", "Number", "String"],+                ) && !args.iter().flatten().any(|arg| arg.spread.is_some()) =>                 {                     report_change!("Dropping a pure new expression"); @@ -2301,7 +2347,7 @@ impl Pure<'_> {                 }) if callee.is_one_of_global_ref_to(                     self.expr_ctx,                     &["Array", "Object", "Boolean", "Number"],-                ) =>+                ) && !args.iter().any(|arg| arg.spread.is_some()) =>                 {                     report_change!("Dropping a pure call expression"); @@ -2691,3 +2737,66 @@ fn is_block_scoped_stmt(s: &Stmt) -> bool {         _ => false,     } }++fn is_valid_map_set_init(expr: &Expr, ctx: ExprCtx, callee: &Expr) -> bool {+    let is_map = callee.is_global_ref_to(ctx, "Map");++    fn is_array_like(expr: &Expr, ctx: ExprCtx) -> bool {+        match expr {+            Expr::Array(..) => true,+            Expr::Call(CallExpr {+                callee: Callee::Expr(e),+                ..+            })+            | Expr::New(NewExpr { callee: e, .. })+                if e.is_one_of_global_ref_to(+                    ctx,+                    &[+                        "Array",+                        "Int16Array",+                        "Int32Array",+                        "Int8Array",+                        "Float32Array",+                        "Float64Array",+                        "Uint16Array",+                        "Uint32Array",+                        "Uint8Array",+                    ],+                ) =>+            {+                true+            }+            _ => false,+        }+    }++    if is_map {+        match expr {+            Expr::Array(ArrayLit { elems, .. }) => elems.iter().all(|e| {+                e.as_ref()+                    .map(|e| e.spread.is_none() && is_array_like(&e.expr, ctx))+                    .unwrap_or(false)+            }),+            Expr::Call(CallExpr {+                callee: Callee::Expr(e),+                args,+                ..+            })+            | Expr::New(NewExpr {+                callee: e,+                args: Some(args),+                ..+            }) if e.is_global_ref_to(ctx, "Array") => args+                .iter()+                .all(|a| a.spread.is_none() && is_array_like(&a.expr, ctx)),+            Expr::New(NewExpr {+                callee: e,+                args: None,+                ..+            }) if e.is_global_ref_to(ctx, "Array") => true,+            _ => false,+        }+    } else {+        is_array_like(expr, ctx)+    }+}

deps/swc/crates/swc_ecma_minifier/src/compress/pure/mod.rs

@@ -103,6 +103,24 @@ impl Pure<'_> {         )     } +    /// Visits the receiver of an optional-chain continuation.+    ///+    /// Only a nested optional chain at this exact edge shares short-circuit+    /// state with its parent. Expressions inside another receiver expression,+    /// such as call arguments, are evaluated independently and must be free+    /// to fold.+    fn visit_opt_chain_continuation(&mut self, e: &mut Expr) {+        if matches!(e, Expr::OptChain(..)) {+            self.do_inside_of_context(Ctx::IN_OPT_CHAIN, |this| {+                e.visit_mut_with(this);+            });+        } else {+            self.do_outside_of_context(Ctx::IN_OPT_CHAIN, |this| {+                e.visit_mut_with(this);+            });+        }+    }+     #[inline(always)]     fn can_simplify_member_expr(e: &MemberExpr) -> bool {         matches!(@@ -1049,7 +1067,11 @@ impl VisitMut for Pure<'_> {             self.do_outside_of_context(                 Ctx::IS_CALLEE                     .union(Ctx::IS_UPDATE_ARG)-                    .union(Ctx::IS_LHS_OF_ASSIGN),+                    .union(Ctx::IS_LHS_OF_ASSIGN)+                    // A computed key is not part of the optional-chain+                    // continuation, so nested optional chains can be folded+                    // independently.+                    .union(Ctx::IN_OPT_CHAIN),                 |this| {                     c.visit_mut_with(this);                 },@@ -1098,15 +1120,59 @@ impl VisitMut for Pure<'_> {             opt_call.callee.visit_mut_with(this);         }); -        opt_call.args.visit_mut_with(self);+        // Arguments are evaluated independently of the optional-chain+        // continuation, unlike the callee.+        self.do_outside_of_context(Ctx::IN_OPT_CHAIN, |this| {+            opt_call.args.visit_mut_with(this);+        });          self.eval_spread_array_in_args(&mut opt_call.args);     }      fn visit_mut_opt_chain_expr(&mut self, e: &mut OptChainExpr) {-        self.do_inside_of_context(Ctx::IN_OPT_CHAIN, |this| {-            e.visit_mut_children_with(this);-        });+        match &mut *e.base {+            OptChainBase::Member(member) => {+                self.do_outside_of_context(Ctx::IS_CALLEE.union(Ctx::IS_UPDATE_ARG), |this| {+                    this.visit_opt_chain_continuation(&mut member.obj);+                });++                if let MemberProp::Computed(c) = &mut member.prop {+                    self.do_outside_of_context(+                        Ctx::IS_CALLEE+                            .union(Ctx::IS_UPDATE_ARG)+                            .union(Ctx::IS_LHS_OF_ASSIGN)+                            .union(Ctx::IN_OPT_CHAIN),+                        |this| {+                            c.visit_mut_with(this);+                        },+                    );++                    // TODO: unify these two+                    if let Some(ident) = self.optimize_property_of_member_expr(Some(&member.obj), c)+                    {+                        member.prop = MemberProp::Ident(ident);+                        return;+                    };++                    if let Some(ident) = self.handle_known_computed_member_expr(c) {+                        member.prop = MemberProp::Ident(ident)+                    };+                }+            }++            OptChainBase::Call(call) => {+                self.do_inside_of_context(Ctx::IS_CALLEE, |this| {+                    this.visit_opt_chain_continuation(&mut call.callee);+                });++                self.do_outside_of_context(Ctx::IN_OPT_CHAIN, |this| {+                    call.args.visit_mut_with(this);+                });+                self.eval_spread_array_in_args(&mut call.args);+            }+            #[cfg(swc_ast_unknown)]+            _ => panic!("unable to access unknown nodes"),+        }     }      fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {

deps/swc/crates/swc_ecma_minifier/src/compress/pure/numbers.rs

@@ -163,7 +163,7 @@ impl Pure<'_> {                 | (op!("|"), Expr::Lit(Lit::Num(n)), Expr::Bin(bin_inner))                     if matches!(                         bin_inner.op,-                        op!("<<") | op!(">>") | op!(">>>") | op!("|") | op!("^") | op!("&")+                        op!("<<") | op!(">>") | op!("|") | op!("^") | op!("&")                     ) && n.value == 0.0 =>                 {                     report_change!("numbers: Turn '(a & b) | 0' into 'a & b'");

deps/swc/crates/swc_ecma_minifier/src/compress/pure/sequences.rs

@@ -1,6 +1,6 @@ use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*;-use swc_ecma_utils::{ExprFactory, StmtLike};+use swc_ecma_utils::{ExprExt, ExprFactory, StmtLike};  use super::Pure; use crate::compress::util::is_pure_undefined;@@ -216,6 +216,18 @@ impl Pure<'_> {             _ => return,         }; +        // Assignment patterns evaluate their right-hand side before evaluating+        // their targets. For simple targets, only identifiers have no evaluation+        // to reorder; moving the conditional test sequence before a member target+        // can change observable base or computed-key evaluation order.+        if !matches!(+            &assign.left,+            AssignTarget::Pat(_) | AssignTarget::Simple(SimpleAssignTarget::Ident(..))+        ) && assign.right.may_have_side_effects(self.expr_ctx)+        {+            return;+        }+         let cond = match &mut *assign.right {             Expr::Cond(v) => v,             _ => return,

deps/swc/crates/swc_ecma_minifier/src/compress/pure/switches.rs

@@ -91,10 +91,17 @@ impl Pure<'_> {          let mut var_ids = Vec::new();         let mut cases = Vec::new();+        let mut removed_case = false;         let mut exact = None;         let mut may_match_other_than_exact = false;+        // Case tests are only evaluated while searching for a match, but their+        // consequents can be entered by falling through from an earlier case.+        let mut may_fall_through = false;+        let mut has_reachable_default = false;          for (idx, case) in stmt.cases.iter_mut().enumerate() {+            let case_terminates = case.cons.iter().rev().any(|s| s.terminates());+             if let Some(test) = case.test.as_ref() {                 if let Some(e) = is_primitive(self.expr_ctx, tail_expr(test)) {                     if match (e, tail) {@@ -108,7 +115,34 @@ impl Pure<'_> {                         exact = Some(idx);                         break;                     } else {-                        var_ids.extend(extract_var_ids(&case.cons))+                        let test = case.test.take().unwrap();++                        if may_fall_through {+                            // A preceding unknown case may have matched and entered this+                            // consequent without evaluating this case test. Keep both the+                            // body and any abrupt completion reachable by that path.+                            case.test = Some(test);+                            cases.push(case.take());+                            may_fall_through = !case_terminates;+                        } else if case.cons.is_empty() && test.may_have_side_effects(self.expr_ctx)+                        {+                            // This case is already a search-only case from an earlier pass.+                            // Retain it unchanged so that compression reaches a fixed point.+                            case.test = Some(test);+                            cases.push(case.take());+                        } else {+                            removed_case = true;+                            var_ids.extend(extract_var_ids(&case.cons));++                            if test.may_have_side_effects(self.expr_ctx) {+                                // Keep the original test in the search path. Its primitive tail is+                                // known not to match, while an expression statement would evaluate+                                // it incorrectly after an earlier fallthrough match.+                                case.test = Some(test);+                                case.cons = Default::default();+                                cases.push(case.take());+                            }+                        }                     }                 } else {                     if !may_match_other_than_exact@@ -118,10 +152,13 @@ impl Pure<'_> {                         may_match_other_than_exact = true;                     } -                    cases.push(case.take())+                    cases.push(case.take());+                    may_fall_through = !case_terminates;                 }             } else {-                cases.push(case.take())+                has_reachable_default |= may_fall_through;+                cases.push(case.take());+                may_fall_through &= !case_terminates;             }         } @@ -137,7 +174,7 @@ impl Pure<'_> {                 }             } -            if !may_match_other_than_exact {+            if !may_match_other_than_exact && !has_reachable_default {                 // remove default if there's an exact match                 cases.retain(|case| {                     if case.test.is_some() {@@ -161,7 +198,7 @@ impl Pure<'_> {             }         } -        if cases.len() == stmt.cases.len() {+        if !removed_case && cases.len() == stmt.cases.len() {             stmt.cases = cases;             return;         }@@ -714,9 +751,9 @@ fn remove_last_break(stmt: &mut Vec<Stmt>) -> bool {             if let Some(h) = t.handler.as_mut() {                 changed |= remove_last_break(&mut h.body.stmts);             }-            if let Some(f) = t.finalizer.as_mut() {-                changed |= remove_last_break(&mut f.stmts);-            }+            // A break in a finalizer replaces a pending throw or return from the try or+            // catch block, so removing it changes the completion of the+            // enclosing switch.             changed         }         Some(Stmt::Block(BlockStmt { stmts, .. })) => remove_last_break(stmts),@@ -764,6 +801,16 @@ impl Visit for BreakFinder {         }     } +    fn visit_try_stmt(&mut self, t: &TryStmt) {+        if self.top_level {+            self.top_level = false;+            t.visit_children_with(self);+            self.top_level = true;+        } else {+            t.visit_children_with(self);+        }+    }+     /// We don't care about breaks in a loop     fn visit_for_stmt(&mut self, _: &ForStmt) {} 

deps/swc/crates/swc_ecma_minifier/src/eval.rs

@@ -2,6 +2,7 @@ use std::sync::Arc;  use parking_lot::Mutex; use rustc_hash::FxHashMap;+use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{ExprCtx, ExprExt};@@ -140,7 +141,7 @@ impl Evaluator {                     }) if tag_obj.is_global_ref_to(self.expr_ctx, "String")                         && prop.sym == *"raw" =>                     {-                        return self.eval_tpl(&t.tpl);+                        return self.eval_tpl_raw(&t.tpl);                     }                      _ => {}@@ -265,6 +266,24 @@ impl Evaluator {     }      pub fn eval_tpl(&mut self, q: &Tpl) -> Option<EvalResult> {+        self.eval_tpl_with_quasis(q, q.quasis.clone())+    }++    fn eval_tpl_raw(&mut self, q: &Tpl) -> Option<EvalResult> {+        let quasis = q+            .quasis+            .iter()+            .cloned()+            .map(|mut quasi| {+                quasi.cooked = Some(cooked_raw_template_value(&quasi.raw));+                quasi+            })+            .collect();++        self.eval_tpl_with_quasis(q, quasis)+    }++    fn eval_tpl_with_quasis(&mut self, q: &Tpl, quasis: Vec<TplElement>) -> Option<EvalResult> {         self.run();          let mut exprs = Vec::new();@@ -280,7 +299,7 @@ impl Evaluator {         let mut e: Box<Expr> = Tpl {             span: q.span,             exprs,-            quasis: q.quasis.clone(),+            quasis,         }         .into(); @@ -308,6 +327,32 @@ impl Evaluator {     } } +/// Returns the cooked value for a raw template quasi with normalized line+/// endings.+fn cooked_raw_template_value(raw: &Atom) -> Wtf8Atom {+    let mut carriage_returns = raw.match_indices('\r');+    let Some((first, _)) = carriage_returns.next() else {+        return raw.clone().into();+    };++    let mut normalized = String::with_capacity(raw.len());+    let mut start = 0;+    let bytes = raw.as_bytes();++    for index in std::iter::once(first).chain(carriage_returns.map(|(index, _)| index)) {+        normalized.push_str(&raw[start..index]);+        normalized.push('\n');+        start = index + 1;++        if bytes.get(start) == Some(&b'\n') {+            start += 1;+        }+    }++    normalized.push_str(&raw[start..]);+    normalized.into()+}+ fn is_truthy(lit: &EvalResult) -> Option<bool> {     match lit {         EvalResult::Lit(v) => match v {

deps/swc/crates/swc_ecma_minifier/src/pass/global_defs.rs

@@ -51,19 +51,13 @@ impl VisitMut for GlobalDefs {             return;         } -        match n {-            Expr::Ident(i) if i.ctxt != self.unresolved_ctxt && i.ctxt != self.top_level_ctxt => {-                return;-            }-            Expr::Ident(..) => {}-            Expr::Member(MemberExpr { obj, .. }) => {-                if let Expr::Ident(i) = &**obj {-                    if i.ctxt != self.unresolved_ctxt && i.ctxt != self.top_level_ctxt {-                        return;-                    }-                }-            }-            _ => {}+        let has_local_root = match n.leftmost() {+            Some(i) => i.ctxt != self.unresolved_ctxt && i.ctxt != self.top_level_ctxt,+            None => false,+        };+        if has_local_root {+            self.visit_mut_computed_props(n);+            return;         }          if let Some((_, new)) = self@@ -79,16 +73,39 @@ impl VisitMut for GlobalDefs {     }      fn visit_mut_update_expr(&mut self, e: &mut UpdateExpr) {-        match &mut *e.arg {-            Expr::Ident(..) => {}--            Expr::Member(MemberExpr { prop, .. }) if !prop.is_computed() => {-                // TODO: Check for `obj`-            }+        self.visit_mut_computed_props(&mut e.arg);+    }+} -            _ => {-                e.arg.visit_mut_with(self);+impl GlobalDefs {+    /// Visits computed property expressions without replacing a protected+    /// member chain or its static property accesses.+    fn visit_mut_computed_props(&mut self, expr: &mut Expr) {+        match expr {+            Expr::Member(MemberExpr { obj, prop, .. }) => {+                self.visit_mut_computed_props(obj);+                if let MemberProp::Computed(prop) = prop {+                    prop.expr.visit_mut_with(self);+                }             }+            Expr::OptChain(OptChainExpr { base, .. }) => match &mut **base {+                OptChainBase::Member(MemberExpr { obj, prop, .. }) => {+                    self.visit_mut_computed_props(obj);+                    if let MemberProp::Computed(prop) = prop {+                        prop.expr.visit_mut_with(self);+                    }+                }+                // Optional calls can be the root of a protected update target,+                // e.g. `(getObject?.())[KEY]++`.+                OptChainBase::Call(..) => expr.visit_mut_children_with(self),+            },+            Expr::Ident(..) => {}+            Expr::Paren(ParenExpr { expr, .. }) => self.visit_mut_computed_props(expr),+            // The root of an update target can be an arbitrary expression, such+            // as `this`, a call, or an object literal. Walk its children so+            // nested expressions can still use global definitions, but do not+            // visit the root itself and replace the protected update target.+            _ => expr.visit_mut_children_with(self),         }     } }

deps/swc/crates/swc_ecma_minifier/src/pass/postcompress.rs

@@ -1,7 +1,7 @@ use std::collections::VecDeque;  use rustc_hash::FxHashMap;-use swc_atoms::{Atom, Wtf8Atom};+use swc_atoms::Wtf8Atom; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; @@ -19,11 +19,13 @@ struct ImportRecord {     /// e.g., `import * as X from 'm.js'` stores X's Id     namespaces: VecDeque<Ident>, -    /// Named imports: stores (import_name, local_id) in order-    /// e.g., `import { foo } from 'm.js'` stores ("foo", foo's Id)-    /// e.g., `import { foo as bar } from 'm.js'` stores ("foo", bar's Id)+    /// Named imports: stores (typed import name, local_id) in order+    /// e.g., `import { foo } from 'm.js'` stores (foo, foo's Id)+    /// e.g., `import { "foo-bar" as bar } from 'm.js'` stores ("foo-bar", bar's+    /// Id)+    ///     /// Note: import_name "default" cases are classified into defaults-    named: VecDeque<(Atom, Ident)>,+    named: VecDeque<(ModuleExportName, Ident)>, }  impl ImportRecord {@@ -82,22 +84,14 @@ pub fn postcompress_optimizer(program: &mut Program, options: &CompressOptions)                             if n.is_type_only {                                 continue;                             }-                            let remote: Atom = n+                            let remote = n                                 .imported-                                .as_ref()-                                .map(|i| match i {-                                    ModuleExportName::Ident(id) => id.sym.clone(),-                                    ModuleExportName::Str(s) => {-                                        Atom::new(s.value.to_string_lossy())-                                    }-                                    #[cfg(swc_ast_unknown)]-                                    _ => panic!("unable to access unknown nodes"),-                                })-                                .unwrap_or_else(|| n.local.sym.clone());+                                .clone()+                                .unwrap_or_else(|| ModuleExportName::Ident(n.local.clone()));                             let local_id = n.local.clone();                              // If remote is "default", classify as default import-                            if &*remote == "default" {+                            if &*remote.atom() == "default" {                                 record.defaults.push_back(local_id);                             } else {                                 record.named.push_back((remote, local_id));@@ -233,12 +227,11 @@ pub fn postcompress_optimizer(program: &mut Program, options: &CompressOptions)                      // All named imports                     for (remote, local) in record.named.drain(..) {-                        let imported = if remote == local.sym {-                            None-                        } else {-                            Some(ModuleExportName::Ident(Ident::new_no_ctxt(-                                remote, DUMMY_SP,-                            )))+                        // A string module export name must remain quoted, even when its value+                        // equals the local binding name.+                        let imported = match &remote {+                            ModuleExportName::Ident(imported) if imported.sym == local.sym => None,+                            _ => Some(remote),                         };                         new_specs.push(ImportSpecifier::Named(ImportNamedSpecifier {                             span: DUMMY_SP,

deps/swc/crates/swc_ecma_minifier/src/usage_analyzer/analyzer/mod.rs

@@ -182,9 +182,7 @@ where                 .store_param_count(Value::Unknown);         } -        if let Pat::Expr(e) = p {-            self.mark_mutation_if_member(e.as_member());-        }+        self.mark_mutation_of_members_in_pat(p);     }      fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value<Type>) {@@ -231,6 +229,16 @@ where         }     } +    /// `o.n` in `[o.n] = arr` or `({ x: o.n } = obj)` mutates `o` just like+    /// `o.n = v` does, but `find_pat_ids` does not report member targets.+    fn mark_mutation_of_members_in_pat<N: VisitWith<MemberTargetsInPat>>(&mut self, n: &N) {+        let mut v = MemberTargetsInPat::default();+        n.visit_with(&mut v);+        for id in v.objs {+            self.data.mark_property_mutation(id);+        }+    }+     fn store_function_arity(&mut self, id: Id, function: &Function) {         let scope = self.data.scope(function.ctxt); @@ -247,6 +255,16 @@ where         self.data.var_or_default(id).store_param_count(arity);     } +    /// Returns true when omitting an argument can evaluate or throw while+    /// initializing this parameter.+    fn has_observable_param_initialization(pat: &Pat) -> bool {+        match pat {+            Pat::Ident(..) => false,+            Pat::Rest(rest) => Self::has_observable_param_initialization(&rest.arg),+            _ => true,+        }+    }+     fn store_arrow_arity(&mut self, id: Id, arrow: &ArrowExpr) {         let scope = self.data.scope(arrow.ctxt);         let known = !scope.used_eval() && !arrow.params.iter().any(|p| p.is_rest());@@ -352,6 +370,7 @@ where                         n.right.get_type(self.expr_ctx),                     )                 }+                self.mark_mutation_of_members_in_pat(p);             }             AssignTarget::Simple(e) => {                 self.report_assign_expr_if_ident(@@ -889,7 +908,13 @@ where         self.with_ctx(ctx)             .declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true); -        if n.function.body.is_empty() {+        if n.function.body.is_empty()+            && !n+                .function+                .params+                .iter()+                .any(|param| Self::has_observable_param_initialization(&param.pat))+        {             self.data.var_or_default(n.ident.to_id()).mark_as_pure_fn();         } @@ -1620,6 +1645,30 @@ where     } } +#[derive(Default)]+struct MemberTargetsInPat {+    objs: Vec<Id>,+}++impl Visit for MemberTargetsInPat {+    noop_visit_type!();++    // Default values and computed keys are not targets. The analyzer visits+    // them itself, including any nested assignment.+    fn visit_expr(&mut self, _: &Expr) {}++    fn visit_pat(&mut self, p: &Pat) {+        if let Pat::Expr(e) = p {+            if let Some(m) = e.as_member() {+                for_each_id_ref_in_expr(&m.obj, &mut |id| self.objs.push(id.to_id()));+            }+            return;+        }++        p.visit_children_with(self);+    }+}+ /// - `a` => `a` /// - `a ? b : c` => `b`, `c` fn for_each_id_ref_in_expr(e: &Expr, op: &mut impl FnMut(&Ident)) {

deps/swc/crates/swc_ecma_parser/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_parser" repository    = { workspace = true }-version       = "45.1.1"+version       = "46.0.0"  [package.metadata.docs.rs] all-features = true@@ -41,7 +41,7 @@ seq-macro      = { workspace = true } serde          = { workspace = true, features = ["derive"] } swc_atoms      = { version = "10.0.0", path = "../swc_atoms" } swc_common     = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast   = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast   = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit", optional = true } tracing        = { workspace = true } @@ -56,7 +56,7 @@ walkdir           = { workspace = true }  cbor4ii = { workspace = true, features = ["use_std"] } codspeed-criterion-compat = { workspace = true }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "serde-impl",   "encoding-impl", ] }

deps/swc/crates/swc_ecma_parser/src/context.rs

@@ -1,78 +1,20 @@ use swc_atoms::Atom;  bitflags::bitflags! {+  /// Lexical state shared with a [`crate::input::Tokens`] implementation.+  ///+  /// Grammar and parser control-flow state are intentionally not exposed here;+  /// they are owned and restored by [`crate::Parser`].   #[derive(Debug, Clone, Copy, Default)]-  pub struct Context: u32 {+  pub struct Context: u8 {        /// `true` while backtracking       const IgnoreError = 1 << 0;        /// Is in module code?       const Module = 1 << 1;-      const CanBeModule = 1 << 2;-      const Strict = 1 << 3;+      const Strict = 1 << 2; -      const ForLoopInit = 1 << 4;-      const ForAwaitLoopInit = 1 << 5;--      const IncludeInExpr = 1 << 6;-      /// If true, await expression is parsed, and "await" is treated as a-      /// keyword.-      const InAsync = 1 << 7;-      /// If true, yield expression is parsed, and "yield" is treated as a-      /// keyword.-      const InGenerator = 1 << 8;--      /// If true, await is treated as a keyword.-      const InStaticBlock = 1 << 9;--      const IsContinueAllowed = 1 << 10;-      const IsBreakAllowed = 1 << 11;--      const InType = 1 << 12;-      /// Typescript extension.-      const ShouldNotLexLtOrGtAsType = 1 << 13;-      /// Typescript extension.-      const InDeclare = 1 << 14;--      /// If true, `:` should not be treated as a type annotation.-      const InCondExpr = 1 << 15;-      const WillExpectColonForCond = 1 << 16;--      const InClass = 1 << 17;--      const InClassField = 1 << 18;--      const InFunction = 1 << 19;--      /// This indicates current scope or the scope out of arrow function is-      /// function declaration or function expression or not.-      const InsideNonArrowFunctionScope = 1 << 20;--      const InParameters = 1 << 21;--      const HasSuperClass = 1 << 22;--      const InPropertyName = 1 << 23;--      const InForcedJsxContext = 1 << 24;--      // If true, allow super.x and super[x]-      const AllowDirectSuper = 1 << 25;--      const IgnoreElseClause = 1 << 26;--      const DisallowConditionalTypes = 1 << 27;--      const AllowUsingDecl = 1 << 28;--      const TopLevel = 1 << 29;--      const TsModuleBlock = 1 << 30;--      /// Flow extension.-      /// Disallow parsing top-level anonymous function types in arrow return type context.-      const DisallowFlowAnonFnType = 1 << 31;   } } @@ -92,12 +34,8 @@ impl Context {             //     let await = 1;             // }             // ```-            "await" => {-                self.contains(Context::InAsync)-                    || self.contains(Context::InStaticBlock)-                    || self.contains(Context::Module)-            }-            "yield" => self.contains(Context::InGenerator) || self.contains(Context::Strict),+            "await" => self.contains(Context::Module),+            "yield" => self.contains(Context::Strict),              "null" | "true" | "false" | "break" | "case" | "catch" | "continue" | "debugger"             | "default" | "do" | "export" | "else" | "finally" | "for" | "function" | "if"

deps/swc/crates/swc_ecma_parser/src/error.rs

@@ -179,6 +179,7 @@ pub enum SyntaxError {     DecoratorOnExport,      TsRequiredAfterOptional,+    TsOptionalRestElement,     TsInvalidParamPropPat,      SpaceBetweenHashAndIdent,@@ -213,6 +214,8 @@ pub enum SyntaxError {      DuplicatedRegExpFlags(char),     UnknownRegExpFlags,+    /// IdentifierPart in RegExp flags must not contain a UnicodeEscapeSequence.+    UnicodeEscapeInRegExpFlags,      TS1003,     TS1005,@@ -260,6 +263,7 @@ pub enum SyntaxError {     TS1243(Atom, Atom),     TS1244,     TS1245,+    TS1263,     TS1267,     TS1273(Atom),     TS1274(Atom),@@ -466,6 +470,9 @@ impl SyntaxError {             SyntaxError::TsRequiredAfterOptional => {                 "A required element cannot follow an optional element.".into()             }+            SyntaxError::TsOptionalRestElement => {+                "A tuple member cannot be both optional and rest.".into()+            }             SyntaxError::SuperCallOptional => "Super call cannot be optional".into(),             SyntaxError::OptChainCannotFollowConstructorCall => {                 "Constructor in/after an optional chaining is not allowed.".into()@@ -592,6 +599,9 @@ impl SyntaxError {                 format!("Duplicated regular expression flag '{flag}'.").into()             }             SyntaxError::UnknownRegExpFlags => "Unknown regular expression flags.".into(),+            SyntaxError::UnicodeEscapeInRegExpFlags => {+                "Regular expression flags cannot contain unicode escapes.".into()+            }              SyntaxError::TS1003 => "Expected an identifier".into(),             SyntaxError::TS1005 => "Expected a semicolon".into(),@@ -670,6 +680,9 @@ impl SyntaxError {                 format!("'{left}' modifier cannot be used with '{right}' modifier.").into()             }             SyntaxError::TS1245 => "Abstract method cannot have an implementation.".into(),+            SyntaxError::TS1263 => "Declarations with initializers cannot also have definite \+                                    assignment assertions."+                .into(),             SyntaxError::TS1267 => "Abstract property cannot have an initializer.".into(),             SyntaxError::TS1273(word) => {                 format!("'{word}' modifier cannot appear on a type parameter").into()

deps/swc/crates/swc_ecma_parser/src/lexer/capturing.rs

@@ -89,6 +89,12 @@ impl<I: Tokens> Tokens for Capturing<I> {         self.inner.read_string(span)     } +    fn rescan_type_gt(&mut self, span: Span) -> TokenAndSpan {+        let token = self.inner.rescan_type_gt(span);+        self.capture(token);+        token+    }+     fn set_ctx(&mut self, ctx: Context) {         self.inner.set_ctx(ctx);     }

deps/swc/crates/swc_ecma_parser/src/lexer/comments_buffer.rs

@@ -42,7 +42,6 @@ impl CommentsBuffer {         self.pending_leading.truncate(checkpoint.pending_leading);     } -    #[cfg(feature = "tsrx")]     pub fn retain_before(&mut self, pos: BytePos) {         self.comments             .retain(|comment| comment.comment.span.lo < pos);

deps/swc/crates/swc_ecma_parser/src/lexer/mod.rs

@@ -66,6 +66,9 @@ static DOUBLE_QUOTE_STRING_END_TABLE: SafeByteMatchTable = static SINGLE_QUOTE_STRING_END_TABLE: SafeByteMatchTable =     safe_byte_match_table!(|b| matches!(b, b'\'' | b'\n' | b'\\' | b'\r')); +static TEMPLATE_END_TABLE: SafeByteMatchTable =+    safe_byte_match_table!(|b| matches!(b, b'`' | b'$' | b'\\' | b'\n' | b'\r'));+ static NOT_ASCII_ID_CONTINUE_TABLE: SafeByteMatchTable =     safe_byte_match_table!(|b| !(b.is_ascii_alphanumeric() || b == b'_' || b == b'$')); @@ -426,17 +429,6 @@ impl Lexer<'_> {         let start = self.cur_pos();         self.bump(1); // first `<` or `>` -        if self.syntax.typescript()-            && self.ctx.contains(Context::InType)-            && !self.ctx.contains(Context::ShouldNotLexLtOrGtAsType)-        {-            if C == b'<' {-                return Ok(Token::Lt);-            } else if C == b'>' {-                return Ok(Token::Gt);-            }-        }-         // XML style comment. `<!--`         if C == b'<'             && self.is(b'!')@@ -530,7 +522,15 @@ impl Lexer<'_> {             }};         } -        while let Some(c) = self.cur() {+        loop {+            let c = byte_search! {+                lexer: self,+                table: TEMPLATE_END_TABLE,+                handle_eof: {+                    return self.error(start, SyntaxError::UnterminatedTpl);+                },+            };+             if c == b'`' {                 consume_cooked!();                 let cooked = cooked.map(|cooked| self.atoms.wtf8_atom(&*cooked));@@ -571,41 +571,30 @@ impl Lexer<'_> {                 }                  cooked_slice_start = self.cur_pos();-            } else if c.is_line_terminator() {+            } else if c == b'$' {+                // A `$` not followed by `{` remains in the cooked slice.+                self.bump(1);+            } else {+                debug_assert!(+                    matches!(c, b'\n' | b'\r'),+                    "template byte search must stop at CR or LF after handling delimiters and \+                     escapes"+                );                 consume_cooked!(); -                // For line terminators, we need the full char (can be multi-byte UTF-8)-                let c_char = if c <= 0x7f {-                    c as char-                } else {-                    self.cur_as_char().unwrap()-                };--                let c = if c == b'\r' && self.peek() == Some(b'\n') {+                // Normalize CR and CRLF to LF in the cooked value.+                if c == b'\r' && self.peek() == Some(b'\n') {                     self.bump(1); // '\r'-                    '\n'-                } else {-                    match c_char {-                        '\n' => '\n',-                        '\r' => '\n',-                        '\u{2028}' => '\u{2028}',-                        '\u{2029}' => '\u{2029}',-                        _ => unreachable!(),-                    }-                };+                } -                self.bump(c_char.len_utf8());+                self.bump(1);                  if let Ok(ref mut cooked) = cooked {-                    cooked.push_char(c);+                    cooked.push_char('\n');                 }                 cooked_slice_start = self.cur_pos();-            } else {-                self.bump(1);             }         }--        self.error(start, SyntaxError::UnterminatedTpl)?     } } @@ -1948,16 +1937,21 @@ impl<'a> Lexer<'a> {         self.bump(1); // '/'          // Spec says "It is a Syntax Error if IdentifierPart contains a Unicode escape-        // sequence." TODO: check for escape--        // Need to use `read_word` because '\uXXXX' sequences are allowed-        // here (don't ask).-        // let flags_start = self.cur_pos();+        // sequence."+        // Need to use `read_word` because '\uXXXX' sequences are accepted by the+        // scanner — we still reject them as an early error below.+        // Use `cur_as_char` so non-ASCII IdentifierPart flags (e.g. `/a/π\u0067`)+        // are scanned; `cur()` only yields the first UTF-8 byte and would miss them.         let flags = {-            match self.cur() {-                Some(c) if c.is_ident_start() => self-                    .read_word_as_str_with()-                    .map(|(s, _)| Some(self.atom(s))),+            match self.cur_as_char() {+                Some(c) if c == '\\' || c.is_ident_part() => {+                    let (s, has_escape) = self.read_word_as_str_with()?;+                    if has_escape {+                        let span = self.span(start);+                        self.emit_error_span(span, SyntaxError::UnicodeEscapeInRegExpFlags);+                    }+                    Ok(Some(self.atom(s)))+                }                 _ => Ok(None),             }         }?;@@ -1993,50 +1987,14 @@ impl<'a> Lexer<'a> {     /// This method is optimized for texts without escape sequences.     fn read_word_as_str_with(&mut self) -> LexResult<(Cow<'a, str>, bool)> {         debug_assert!(self.cur().is_some());-        let slice_start = self.cur_pos(); -        // Fast path: try to scan ASCII identifier using byte_search         if let Some(c) = self.input().cur_as_ascii() {             if Ident::is_valid_ascii_start(c) {-                // Use byte_search to quickly scan to end of ASCII identifier-                let next_byte = byte_search! {-                    lexer: self,-                    table: NOT_ASCII_ID_CONTINUE_TABLE,-                    start_at: 1,-                    handle_eof: {-                        // Reached EOF, entire remainder is identifier-                        let s = unsafe {-                            // Safety: slice_start and end are valid position because we got them from-                            // `self.input`-                            self.input_slice_str(slice_start, self.cur_pos())-                        };--                        return Ok((Cow::Borrowed(s), false));-                    },-                };--                // Check if we hit end of identifier or need to fall back to slow path-                if !next_byte.is_ascii() {-                    // Hit Unicode character, fall back to slow path from current position-                    return self.read_word_as_str_with_slow_path(slice_start);-                } else if next_byte == b'\\' {-                    // Hit escape sequence, fall back to slow path from current position-                    return self.read_word_as_str_with_slow_path(slice_start);-                } else {-                    // Hit end of identifier (non-continue ASCII char)-                    let s = unsafe {-                        // Safety: slice_start and end are valid position because we got them from-                        // `self.input`-                        self.input_slice_str(slice_start, self.cur_pos())-                    };--                    return Ok((Cow::Borrowed(s), false));-                }+                return self.read_ascii_word_as_str();             }         } -        // Fall back to slow path for non-ASCII start or complex cases-        self.read_word_as_str_with_slow_path(slice_start)+        self.read_word_as_str_with_slow_path(self.cur_pos())     }      /// Slow path for identifier parsing that handles Unicode and escapes@@ -2048,7 +2006,8 @@ impl<'a> Lexer<'a> {         let mut first = true;         let mut has_escape = false; -        let mut buf = String::with_capacity(16);+        // Unescaped Unicode identifiers can borrow the source just like ASCII ones.+        let mut buf = String::new();         loop {             if let Some(c) = self.input().cur_as_ascii() {                 if Ident::is_valid_ascii_continue(c) {@@ -2063,6 +2022,9 @@ impl<'a> Lexer<'a> {                 // unicode escape                 if c == b'\\' {                     first = false;+                    if !has_escape {+                        buf.reserve(16);+                    }                     has_escape = true;                     let start = self.cur_pos();                     self.bump(1); // `\`@@ -2464,36 +2426,25 @@ impl<'a> Lexer<'a> {     fn read_keyword_with(&mut self, convert: fn(&str) -> Option<Token>) -> LexResult<Token> {         debug_assert!(self.cur().is_some()); -        let start = self.cur_pos();-        let (s, has_escape) = self.read_keyword_as_str_with()?;+        let (s, has_escape) = self.read_ascii_word_as_str()?;         if let Some(word) = convert(s.as_ref()) {-            // Note: ctx is store in lexer because of this error.-            // 'await' and 'yield' may have semantic of reserved word, which means lexer-            // should know context or parser should handle this error. Our approach to this-            // problem is former one.-            if has_escape && word.is_reserved(self.ctx()) {-                self.error(-                    start,-                    SyntaxError::EscapeInReservedWord { word: Atom::new(s) },-                )-            } else {-                Ok(word)-            }+            // Keep escaped keyword spellings until the parser knows whether this+            // is a keyword or an IdentifierName (for example, a property name).+            let value = has_escape.then(|| TokenValue::Word(self.atom(s)));+            self.set_token_value(value);+            Ok(word)         } else {             let atom = self.atom(s);             Ok(Token::unknown_ident(atom, self))         }     } -    /// This is a performant version of [Lexer::read_word_as_str_with] for-    /// reading keywords. We should make sure the first byte is a valid-    /// ASCII.-    fn read_keyword_as_str_with(&mut self) -> LexResult<(Cow<'a, str>, bool)> {+    /// Reads a word whose first byte is known to be an ASCII identifier start.+    /// Unicode and escapes after the ASCII prefix use the shared slow path.+    #[inline(always)]+    fn read_ascii_word_as_str(&mut self) -> LexResult<(Cow<'a, str>, bool)> {         let slice_start = self.cur_pos(); -        // Fast path: try to scan ASCII identifier using byte_search-        // Performance optimization: check if first char disqualifies as keyword-        // Use byte_search to quickly scan to end of ASCII identifier         let next_byte = byte_search! {             lexer: self,             table: NOT_ASCII_ID_CONTINUE_TABLE,

deps/swc/crates/swc_ecma_parser/src/lexer/number.rs

@@ -17,70 +17,90 @@ pub(super) fn parse_integer<const RADIX: u8>(s: &str) -> f64 {      if RADIX == 10 {         parse_integer_from_dec(s)-    } else if RADIX == 16 {-        parse_integer_from_hex(s)-    } else if RADIX == 2 {-        parse_integer_from_bin(s)-    } else if RADIX == 8 {-        parse_integer_from_oct(s)     } else {-        unreachable!()+        parse_integer_radix::<RADIX>(s)     } } -fn parse_integer_from_hex(s: &str) -> f64 {-    debug_assert!(s.chars().all(|c| c.is_ascii_hexdigit()));-    const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog(16) as usize;-    if s.len() > MAX_FAST_INT_LEN {-        // Hex digit character representations:-        //   b'0'==0x30, b'1'==0x31 ... b'9'==0x39  → low nibble: 0x0-0x9-        //   b'A'==0x41, b'B'==0x42 ... b'F'==0x46  → low nibble: 0x1-0x6-        //   b'a'==0x61, b'b'==0x62 ... b'f'==0x66  → low nibble: 0x1-0x6-        //-        // Conversion requires only the low 4 bits:-        //   digit_char & 0x0F gives base value:-        //     - For '0'-'9': direct value (0-9)-        //     - For 'A'-'F'/'a'-'f': offset base (1-6)-        //   Add 9 for alphabetic chars: (low_nibble + 9) → 0xA-0xF-        //-        // Example: (b'A' & 0x0f) + 9 == 0x1 + 9 == 0xA-        s.as_bytes().iter().fold(0f64, |res, &cur| {-            res.mul_add(-                16.,-                if cur < b'A' {-                    cur & 0xf-                } else {-                    (cur & 0xf) + 9-                } as f64,-            )-        })+fn parse_integer_radix<const RADIX: u8>(s: &str) -> f64 {+    debug_assert!(s.chars().all(|c| c.is_digit(RADIX as u32)));++    // Accumulate integers that fit in u64 exactly, then round only once.+    let max_fast_len = (u64::BITS / RADIX.trailing_zeros()) as usize;+    if s.len() <= max_fast_len {+        u64::from_str_radix(s, RADIX as u32).unwrap() as f64     } else {-        u64::from_str_radix(s, 16).unwrap() as f64+        parse_radix_slow::<RADIX>(s)     } } -fn parse_integer_from_bin(s: &str) -> f64 {-    debug_assert!(s.chars().all(|c| c == '0' || c == '1'));-    const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog2() as usize;-    if s.len() > MAX_FAST_INT_LEN {-        s.as_bytes().iter().fold(0f64, |res, &cur| {-            res.mul_add(2., if cur == b'0' { 0. } else { 1. })-        })+#[cold]+#[inline(never)]+fn parse_radix_slow<const RADIX: u8>(raw: &str) -> f64 {+    debug_assert!(RADIX.is_power_of_two());++    // These radices encode an integer as fixed-width bit groups. Retain its+    // leading significand plus guard and sticky bits, then round exactly once.+    const SIGNIFICAND_BITS: usize = f64::MANTISSA_DIGITS as usize;+    const MAX_EXPONENT: usize = f64::MAX_EXP as usize - 1;+    const EXPONENT_BIAS: u64 = 1023;++    let raw = raw.trim_start_matches('0');+    if raw.is_empty() {+        return 0.0;+    }++    let bits_per_digit = RADIX.trailing_zeros() as usize;+    // Bound the length before multiplication, including inputs larger than usize+    // can represent in bits. One leading digit may use fewer bits than the rest.+    if raw.len() > (MAX_EXPONENT + 1) / bits_per_digit + 1 {+        return f64::INFINITY;+    }+    let first = raw.as_bytes()[0];+    let first = if RADIX == 16 && first >= b'A' {+        (first & 15) + 9     } else {-        u64::from_str_radix(s, 2).unwrap() as f64+        first & 15+    };+    let first_bits = (u8::BITS - first.leading_zeros()) as usize;+    let bit_length = (raw.len() - 1) * bits_per_digit + first_bits;+    if bit_length > MAX_EXPONENT + 1 {+        return f64::INFINITY;+    }+    if bit_length <= SIGNIFICAND_BITS {+        return u64::from_str_radix(raw, RADIX as u32).unwrap() as f64;     }-} -fn parse_integer_from_oct(s: &str) -> f64 {-    debug_assert!(s.chars().all(|c| matches!(c, '0'..='7')));-    const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog(8) as usize;-    if s.len() > MAX_FAST_INT_LEN {-        s.as_bytes()+    // Read enough whole digits for the significand and guard bit. Any extra+    // bits in that prefix and all remaining digits contribute to the sticky bit.+    let prefix_len = 1 + (SIGNIFICAND_BITS + 1 - first_bits).div_ceil(bits_per_digit);+    let prefix = u64::from_str_radix(&raw[..prefix_len], RADIX as u32).unwrap();+    let prefix_bits = first_bits + (prefix_len - 1) * bits_per_digit;+    let discarded_bits = prefix_bits - SIGNIFICAND_BITS;+    let mut significand = prefix >> discarded_bits;+    let guard_mask = 1_u64 << (discarded_bits - 1);+    let guard = prefix & guard_mask != 0;+    let sticky = prefix & (guard_mask - 1) != 0+        || raw.as_bytes()[prefix_len..]             .iter()-            .fold(0f64, |res, &cur| res.mul_add(8., (cur - b'0') as f64))-    } else {-        u64::from_str_radix(s, 8).unwrap() as f64+            .any(|&byte| byte != b'0');++    let round_up = guard && (sticky || significand & 1 != 0);+    let mut exponent = bit_length - 1;+    if round_up {+        significand += 1;+        if significand == 1 << SIGNIFICAND_BITS {+            significand >>= 1;+            exponent += 1;+        }+    }+    if exponent > MAX_EXPONENT {+        return f64::INFINITY;     }++    let exponent = exponent as u64 + EXPONENT_BIAS;+    let fraction_mask = (1_u64 << (SIGNIFICAND_BITS - 1)) - 1;+    f64::from_bits((exponent << (SIGNIFICAND_BITS - 1)) | (significand & fraction_mask)) }  fn parse_integer_from_dec(s: &str) -> f64 {

deps/swc/crates/swc_ecma_parser/src/lexer/state.rs

@@ -4,9 +4,7 @@ use swc_atoms::{     wtf8::{CodePoint, Wtf8, Wtf8Buf},     Wtf8Atom, };-#[cfg(feature = "tsrx")]-use swc_common::Spanned;-use swc_common::{BytePos, Span};+use swc_common::{BytePos, Span, Spanned}; use swc_ecma_ast::EsVersion;  use super::{Context, Input, Lexer};@@ -254,6 +252,32 @@ impl crate::input::Tokens for Lexer<'_> {         self.finish_next_token(self.span(start), token)     } +    // Most type closers are already a single `>`. Keep suffix rescanning and+    // speculative lookahead cleanup out of the common closer's call frame.+    #[cold]+    #[inline(never)]+    fn rescan_type_gt(&mut self, span: Span) -> TokenAndSpan {+        let start = span.lo;+        let end = start + BytePos(1);+        // Without lookahead, no diagnostics or comments can follow this+        // punctuation token. Avoid scanning accumulated buffers on that path.+        if self.cur_pos() > span.hi {+            self.errors.retain(|error| error.span().lo < end);+            self.module_errors.retain(|error| error.span().lo < end);+            if let Some(comments) = self.comments_buffer.as_mut() {+                comments.retain_before(end);+            }+        }+        // The parser only calls this for an ASCII greater-than operator.+        unsafe {+            self.input.reset_to(end);+        }+        self.state.token_value = None;+        self.state.next_regexp = None;+        self.token_flags = TokenFlags::empty();+        self.finish_next_token(Span::new_with_checked(start, end), Token::Gt)+    }+     fn rescan_jsx_token(&mut self, reset: BytePos) -> TokenAndSpan {         #[cfg(feature = "tsrx")]         {

deps/swc/crates/swc_ecma_parser/src/lexer/token.rs

@@ -13,7 +13,7 @@ use crate::{  #[derive(Debug, Clone)] pub enum TokenValue {-    /// unknown ident, jsx name and shebang+    /// Unknown identifiers, escaped keyword spellings, JSX names, and shebangs.     Word(Atom),     Template(LexResult<Wtf8Atom>),     // string@@ -375,11 +375,7 @@ impl<'a> Token {      #[inline(always)]     pub fn take_word<I: Tokens>(self, buffer: &Buffer<I>) -> Atom {-        if self == Token::Ident {-            let value = buffer.get_token_value();-            let Some(TokenValue::Word(word)) = value else {-                unreachable!("{:#?}", value)-            };+        if let Some(TokenValue::Word(word)) = buffer.get_token_value() {             return word.clone();         } @@ -390,9 +386,7 @@ impl<'a> Token {      #[inline(always)]     pub fn take_known_ident<I: Tokens>(self, buffer: &Buffer<I>) -> Atom {-        let span = buffer.cur.span;-        let atom = Atom::new(buffer.iter.read_string(span));-        atom+        self.take_word(buffer)     }      #[inline(always)]@@ -437,12 +431,8 @@ impl Token {     pub const fn is_reserved(self, ctx: Context) -> bool {         match self {             Token::Let | Token::Static => ctx.contains(Context::Strict),-            Token::Await => {-                ctx.contains(Context::InAsync)-                    || ctx.contains(Context::InStaticBlock)-                    || ctx.contains(Context::Strict)-            }-            Token::Yield => ctx.contains(Context::InGenerator) || ctx.contains(Context::Strict),+            Token::Await => ctx.contains(Context::Module),+            Token::Yield => ctx.contains(Context::Strict),              Token::Null             | Token::True

deps/swc/crates/swc_ecma_parser/src/lib.rs

@@ -261,8 +261,7 @@ macro_rules! expose {  expose!(parse_file_as_expr, Box<Expr>, |p| {     // This allow to parse `import.meta`-    let ctx = p.ctx();-    p.set_ctx(ctx.union(Context::CanBeModule));+    p.allow_module_syntax();     p.parse_expr() }); expose!(parse_file_as_module, Module, |p| { p.parse_module() });

deps/swc/crates/swc_ecma_parser/src/parser/class_and_fn.rs

@@ -10,9 +10,10 @@ use crate::{     lexer::Token,     parser::{         state::State,-        util::{IsInvalidClassName, IsSimpleParameterList},+        util::{is_ts_ambient_initializer, IsInvalidClassName, IsSimpleParameterList},+        BoundaryContext, FunctionKind, StatementContext, SyntaxContext, TypeContext,     },-    Context, PResult, Parser,+    PResult, Parser, };  struct MakeMethodArgs {@@ -55,8 +56,8 @@ fn parse_ts_this_param<I: Tokens>(p: &mut Parser<I>) -> PResult<Option<Box<TsThi     let type_ann = p.try_parse_ts_type_ann()?;     if is_flow         && type_ann.is_none()-        && !p.ctx().contains(Context::InType)-        && !p.ctx().contains(Context::InDeclare)+        && !p.type_ctx().contains(TypeContext::InType)+        && !p.type_ctx().contains(TypeContext::InDeclare)     {         p.emit_err(this_span, SyntaxError::TS1003);     }@@ -71,7 +72,7 @@ fn parse_ts_this_param<I: Tokens>(p: &mut Parser<I>) -> PResult<Option<Box<TsThi             };             p.emit_err(p.input().prev_span(), error);         }-        p.allow_in_expr(|p| p.parse_assignment_expr())?;+        with_grammar_context!(p, [+In, ?Yield, ?Await], |p| p.parse_assignment_expr())?;     }      if !p.input().is(Token::RParen) {@@ -110,11 +111,25 @@ impl<I: Tokens> Parser<I> {         }     } +    /// Ambient function names, unlike ambient variables, may be `eval` or+    /// `arguments` even in a strict-mode module.+    fn parse_maybe_opt_function_ident(&mut self, required: bool) -> PResult<Option<Ident>> {+        if self.syntax().typescript()+            && !self.syntax().flow()+            && self.type_ctx().contains(TypeContext::InDeclare)+            && self.input().is(Token::Ident)+        {+            let word = self.input().cur().take_word(self.input());+            if word == atom!("eval") || word == atom!("arguments") {+                return self.parse_ident(true, true).map(Some);+            }+        }+        self.parse_maybe_opt_binding_ident(required, false)+    }+     fn parse_maybe_decorator_args(&mut self, expr: Box<Expr>) -> PResult<Box<Expr>> {         let type_args = if self.input().syntax().typescript() && self.input().is(Token::Lt) {-            let ret = self.parse_ts_type_args()?;-            self.assert_and_bump(Token::Gt);-            Some(ret)+            Some(self.parse_ts_type_args()?)         } else {             None         };@@ -153,22 +168,22 @@ impl<I: Tokens> Parser<I> {         }          if self.input().is(Token::Export) {-            if !self.ctx().contains(Context::InClass)-                && !self.ctx().contains(Context::InFunction)+            if !self.boundary_ctx().contains(BoundaryContext::InClass)+                && self.syntax_context != SyntaxContext::FunctionBody                 && !allow_export             {                 syntax_error!(self, self.input().cur_span(), SyntaxError::ExportNotAllowed);             } -            if !self.ctx().contains(Context::InClass)-                && !self.ctx().contains(Context::InFunction)+            if !self.boundary_ctx().contains(BoundaryContext::InClass)+                && self.syntax_context != SyntaxContext::FunctionBody                 && !self.syntax().decorators_before_export()             {                 syntax_error!(self, self.span(start), SyntaxError::DecoratorOnExport);             }         } else if self.syntax().flow_decorators()-            && !self.ctx().contains(Context::InClass)-            && !self.ctx().contains(Context::InFunction)+            && !self.boundary_ctx().contains(BoundaryContext::InClass)+            && self.syntax_context != SyntaxContext::FunctionBody             && !self.input().is(Token::Class)         {             syntax_error!(self, self.span(start), SyntaxError::InvalidLeadingDecorator)@@ -269,7 +284,6 @@ impl<I: Tokens> Parser<I> {                 // but it's a super class with type params, for example, in JSX.                 if self.syntax().typescript() && self.input().is(Token::Lt) {                     let ret = self.parse_ts_type_args()?;-                    self.assert_and_bump(Token::Gt);                     Ok((super_class, Some(ret)))                 } else {                     Ok((super_class, None))@@ -322,7 +336,7 @@ impl<I: Tokens> Parser<I> {          if self.input_mut().eat(Token::LParen) {             if !self.input().is(Token::RParen) {-                self.allow_in_expr(Self::parse_assignment_expr)?;+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;             }             expect!(self, Token::RParen);         }@@ -362,27 +376,15 @@ impl<I: Tokens> Parser<I> {              expect!(p, Token::LParen); -            let parse_args_with_generator_ctx = |p: &mut Self| {-                let parse_this_and_args = |p: &mut Self| {-                    let this_param = parse_ts_this_param(p)?;-                    parse_args(p).map(|params| (this_param, params))-                };--                if is_generator {-                    p.do_inside_of_context(Context::InGenerator, parse_this_and_args)-                } else {-                    p.do_outside_of_context(Context::InGenerator, parse_this_and_args)-                }-            };-             let (this_param, params) = p.without_async_arrow_param_await_collection(|p| {-                p.do_inside_of_context(Context::InParameters, |p| {-                    p.do_outside_of_context(Context::InFunction, |p| {-                        if is_async {-                            p.do_inside_of_context(Context::InAsync, parse_args_with_generator_ctx)-                        } else {-                            p.do_outside_of_context(Context::InAsync, parse_args_with_generator_ctx)-                        }+                p.do_inside_of_boundary_context(BoundaryContext::InParameters, |p| {+                    p.with_syntax_context(SyntaxContext::Parameters, |p| {+                        // FormalParameters use the function kind's derived arguments;+                        // their FormalParameter children inherit both parameters.+                        with_grammar_context!(p, [?Yield, ?Await], |p| {+                            let this_param = parse_ts_this_param(p)?;+                            parse_args(p).map(|params| (this_param, params))+                        })                     })                 })             })?;@@ -413,13 +415,15 @@ impl<I: Tokens> Parser<I> {             }              let body: Option<_> = p.parse_fn_block_body(-                is_async,-                is_generator,-                false,+                FunctionKind::Function {+                    is_async,+                    is_generator,+                },                 params.is_simple_parameter_list(),             )?; -            if p.syntax().flow() && body.is_none() && !p.ctx().contains(Context::InDeclare) {+            if p.syntax().flow() && body.is_none() && !p.type_ctx().contains(TypeContext::InDeclare)+            {                 p.emit_err(p.input().cur_span(), SyntaxError::TS1005);             } @@ -453,19 +457,17 @@ impl<I: Tokens> Parser<I> {             }))         }; -        let f_with_generator_ctx = |p: &mut Self| {-            if is_generator {-                p.do_inside_of_context(Context::InGenerator, f)-            } else {-                p.do_outside_of_context(Context::InGenerator, f)+        // Ordinary functions and methods establish their own Await grammar+        // parameter, including their parameter lists, inside static blocks.+        self.do_outside_of_boundary_context(BoundaryContext::InStaticBlock, |p| {+            // Function, generator, async-function, and async-generator parameters.+            match (is_generator, is_async) {+                (false, false) => with_grammar_context!(p, [~Yield, ~Await], f),+                (true, false) => with_grammar_context!(p, [+Yield, ~Await], f),+                (false, true) => with_grammar_context!(p, [~Yield, +Await], f),+                (true, true) => with_grammar_context!(p, [+Yield, +Await], f),             }-        };--        if is_async {-            self.do_inside_of_context(Context::InAsync, f_with_generator_ctx)-        } else {-            self.do_outside_of_context(Context::InAsync, f_with_generator_ctx)-        }+        })     }      pub(crate) fn parse_async_fn_expr(&mut self) -> PResult<Box<Expr>> {@@ -522,52 +524,51 @@ impl<I: Tokens> Parser<I> {         let is_generator = self.input_mut().eat(Token::Asterisk);          let ident = if is_fn_expr {-            let f_with_generator_context = |p: &mut Self| {-                if is_generator {-                    p.do_inside_of_context(Context::InGenerator, |p| {-                        p.parse_maybe_opt_binding_ident(is_ident_required, false)-                    })-                } else {-                    p.do_outside_of_context(Context::InGenerator, |p| {-                        p.parse_maybe_opt_binding_ident(is_ident_required, false)-                    })-                }-            };--            self.do_outside_of_context(-                Context::AllowDirectSuper.union(Context::InClassField),+            self.do_outside_of_boundary_context(+                BoundaryContext::AllowDirectSuper+                    | BoundaryContext::InClassField+                    | BoundaryContext::InStaticBlock,                 |p| {-                    if is_async {-                        p.do_inside_of_context(Context::InAsync, f_with_generator_context)-                    } else {-                        p.do_outside_of_context(Context::InAsync, f_with_generator_context)+                    // Expression names use the function kind's BindingIdentifier+                    // arguments; declaration names inherit the enclosing grammar.+                    let parse_name =+                        |p: &mut Self| p.parse_maybe_opt_function_ident(is_ident_required);+                    match (is_generator, is_async) {+                        (false, false) => with_grammar_context!(p, [~Yield, ~Await], parse_name),+                        (true, false) => with_grammar_context!(p, [+Yield, ~Await], parse_name),+                        (false, true) => with_grammar_context!(p, [~Yield, +Await], parse_name),+                        (true, true) => with_grammar_context!(p, [+Yield, +Await], parse_name),                     }                 },             )?         } else {             // function declaration does not change context for `BindingIdentifier`.-            self.do_outside_of_context(-                Context::AllowDirectSuper.union(Context::InClassField),-                |p| p.parse_maybe_opt_binding_ident(is_ident_required, false),+            self.do_outside_of_boundary_context(+                BoundaryContext::AllowDirectSuper.union(BoundaryContext::InClassField),+                |p| {+                    with_grammar_context!(p, [?Yield, ?Await], |p| {+                        p.parse_maybe_opt_function_ident(is_ident_required)+                    })+                },             )?         }; -        self.do_outside_of_context(-            Context::AllowDirectSuper-                .union(Context::InClassField)-                .union(Context::WillExpectColonForCond),+        self.do_outside_of_boundary_context(+            BoundaryContext::AllowDirectSuper | BoundaryContext::InClassField,             |p| {-                let f = p.parse_fn_args_body(-                    decorators,-                    start,-                    Self::parse_formal_params,-                    is_async,-                    is_generator,-                )?;-                if is_fn_expr && f.body.is_none() {-                    unexpected!(p, "{");-                }-                Ok((ident, f))+                p.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                    let f = p.parse_fn_args_body(+                        decorators,+                        start,+                        Self::parse_formal_params,+                        is_async,+                        is_generator,+                    )?;+                    if is_fn_expr && f.body.is_none() {+                        unexpected!(p, "{");+                    }+                    Ok((ident, f))+                })             },         )     }@@ -647,11 +648,12 @@ impl<I: Tokens> Parser<I> {         trace_cur!(self, make_method);          let is_static = static_token.is_some();-        let function = self.do_inside_of_context(Context::AllowDirectSuper, |p| {-            p.do_outside_of_context(Context::InClassField, |p| {-                p.parse_fn_args_body(decorators, start, parse_args, is_async, is_generator)-            })-        })?;+        let function =+            self.do_inside_of_boundary_context(BoundaryContext::AllowDirectSuper, |p| {+                p.do_outside_of_boundary_context(BoundaryContext::InClassField, |p| {+                    p.parse_fn_args_body(decorators, start, parse_args, is_async, is_generator)+                })+            })?;          if self.syntax().flow()             && matches!(kind, MethodKind::Getter | MethodKind::Setter)@@ -717,17 +719,13 @@ impl<I: Tokens> Parser<I> {         }     } -    pub(crate) fn parse_fn_block_or_expr_body(+    pub(super) fn parse_fn_block_or_expr_body(         &mut self,-        is_async: bool,-        is_generator: bool,-        is_arrow_function: bool,+        kind: FunctionKind,         is_simple_parameter_list: bool,     ) -> PResult<Box<ArrowFunctionBody>> {         self.parse_fn_body(-            is_async,-            is_generator,-            is_arrow_function,+            kind,             is_simple_parameter_list,             |p, is_simple_parameter_list| {                 if p.input().is(Token::LBrace) {@@ -754,9 +752,7 @@ impl<I: Tokens> Parser<I> {      fn parse_fn_body<T>(         &mut self,-        is_async: bool,-        is_generator: bool,-        is_arrow_function: bool,+        kind: FunctionKind,         is_simple_parameter_list: bool,         f: impl FnOnce(&mut Self, bool) -> PResult<T>,     ) -> PResult<T> {@@ -766,10 +762,10 @@ impl<I: Tokens> Parser<I> {             || self.syntax().tsrx()                 && self.input().is(Token::At)                 && peek!(self).is_some_and(|token| token == Token::LBrace);-        if self.ctx().contains(Context::InDeclare)+        if self.type_ctx().contains(TypeContext::InDeclare)             && self.syntax().typescript()             && has_explicit_body-            && (!self.syntax().flow() || !self.ctx().contains(Context::TsModuleBlock))+            && (!self.syntax().flow() || !self.type_ctx().contains(TypeContext::TsModuleBlock))         {             //            self.emit_err(             //                self.ctx().span_of_fn_name.expect("we are not in function"),@@ -778,55 +774,67 @@ impl<I: Tokens> Parser<I> {             self.emit_err(self.input().cur_span(), SyntaxError::TS1183);         } -        let f_with_generator_context = |p: &mut Self| {-            let f_with_inside_non_arrow_fn_scope = |p: &mut Self| {-                let f_with_new_state = |p: &mut Self| {-                    let mut p = p.with_state(State::default());-                    f(&mut p, is_simple_parameter_list)-                };--                if is_arrow_function && !p.ctx().contains(Context::InsideNonArrowFunctionScope) {-                    p.do_outside_of_context(Context::InsideNonArrowFunctionScope, f_with_new_state)-                } else {-                    p.do_inside_of_context(Context::InsideNonArrowFunctionScope, f_with_new_state)-                }+        let f_with_function_scope = |p: &mut Self| {+            let f_with_new_state = |p: &mut Self| {+                let mut p = p.with_state(State::default());+                f(&mut p, is_simple_parameter_list)             }; -            if is_generator {-                p.do_inside_of_context(Context::InGenerator, f_with_inside_non_arrow_fn_scope)+            if kind.is_arrow()+                && !p+                    .boundary_ctx()+                    .contains(BoundaryContext::InsideNonArrowFunctionScope)+            {+                p.do_outside_of_boundary_context(+                    BoundaryContext::InsideNonArrowFunctionScope,+                    f_with_new_state,+                )             } else {-                p.do_outside_of_context(Context::InGenerator, f_with_inside_non_arrow_fn_scope)+                p.do_inside_of_boundary_context(+                    BoundaryContext::InsideNonArrowFunctionScope,+                    f_with_new_state,+                )             }         }; -        self.do_inside_of_context(Context::InFunction, |p| {-            p.do_outside_of_context(-                Context::InStaticBlock-                    .union(Context::IsBreakAllowed)-                    .union(Context::IsContinueAllowed)-                    .union(Context::TopLevel),-                |p| {-                    if is_async {-                        p.do_inside_of_context(Context::InAsync, f_with_generator_context)-                    } else {-                        p.do_outside_of_context(Context::InAsync, f_with_generator_context)-                    }-                },-            )+        with_grammar_context!(self, [?Yield, ?Await, +Return], |p| {+            p.with_syntax_context(SyntaxContext::FunctionBody, |p| {+                p.do_outside_of_boundary_context(+                    BoundaryContext::InStaticBlock | BoundaryContext::TopLevel,+                    |p| {+                        p.do_outside_of_statement_context(+                            StatementContext::IsBreakAllowed | StatementContext::IsContinueAllowed,+                            |p| {+                                // FunctionBody arguments selected by the enclosing production.+                                match (kind.is_generator(), kind.is_async()) {+                                    (false, false) => with_grammar_context!(+                                        p, [~Yield, ~Await], f_with_function_scope+                                    ),+                                    (true, false) => with_grammar_context!(+                                        p, [+Yield, ~Await], f_with_function_scope+                                    ),+                                    (false, true) => with_grammar_context!(+                                        p, [~Yield, +Await], f_with_function_scope+                                    ),+                                    (true, true) => with_grammar_context!(+                                        p, [+Yield, +Await], f_with_function_scope+                                    ),+                                }+                            },+                        )+                    },+                )+            })         })     }      pub(super) fn parse_fn_block_body(         &mut self,-        is_async: bool,-        is_generator: bool,-        is_arrow_function: bool,+        kind: FunctionKind,         is_simple_parameter_list: bool,     ) -> PResult<Option<FunctionBody>> {         self.parse_fn_body(-            is_async,-            is_generator,-            is_arrow_function,+            kind,             is_simple_parameter_list,             |p, is_simple_parameter_list| {                 #[cfg(feature = "tsrx")]@@ -842,8 +850,8 @@ impl<I: Tokens> Parser<I> {                 // allow omitting body and allow placing `{` on next line                 let has_explicit_body_terminator = p.input_mut().eat(Token::Semi)                     || (p.syntax().flow()-                        && p.ctx().contains(Context::InDeclare)-                        && p.ctx().contains(Context::InClass)+                        && p.type_ctx().contains(TypeContext::InDeclare)+                        && p.boundary_ctx().contains(BoundaryContext::InClass)                         && p.input_mut().eat(Token::Comma));                 let omitted_body_terminator = has_explicit_body_terminator                     || p.input().is(Token::RBrace)@@ -857,14 +865,15 @@ impl<I: Tokens> Parser<I> {                 {                     return Ok(None);                 }-                p.allow_in_expr(|p| p.parse_block(true)).map(|block_stmt| {-                    if !is_simple_parameter_list {-                        if let Some(span) = has_use_strict(&block_stmt) {-                            p.emit_err(span, SyntaxError::IllegalLanguageModeDirective);+                with_grammar_context!(p, [+In, ?Yield, ?Await, ?Return], |p| p.parse_block(true))+                    .map(|block_stmt| {+                        if !is_simple_parameter_list {+                            if let Some(span) = has_use_strict(&block_stmt) {+                                p.emit_err(span, SyntaxError::IllegalLanguageModeDirective);+                            }                         }-                    }-                    Some(function_body_from_block_stmt(block_stmt))-                })+                        Some(function_body_from_block_stmt(block_stmt))+                    })             },         )     }@@ -886,6 +895,21 @@ impl<I: Tokens> Parser<I> {         if is_constructor(&key) {             syntax_error!(self, key.span(), SyntaxError::PropertyNamedConstructor);         }+        if self.syntax().typescript() {+            if let Some(span) = accessor_token {+                if declare {+                    self.emit_err(+                        span,+                        SyntaxError::TS1243(atom!("accessor"), atom!("declare")),+                    );+                } else if readonly {+                    self.emit_err(+                        span,+                        SyntaxError::TS1243(atom!("accessor"), atom!("readonly")),+                    );+                }+            }+        }         if key.is_private() {             if declare && !self.syntax().flow() {                 self.emit_err(@@ -905,128 +929,174 @@ impl<I: Tokens> Parser<I> {          let type_ann = self.try_parse_ts_type_ann()?; -        self.do_inside_of_context(Context::IncludeInExpr.union(Context::InClassField), |p| {-            // Class field initializers have their own Await grammar parameter and must-            // not inherit the unambiguous Program probe's top-level async context.-            let value = p.without_async_arrow_param_await_collection(|p| {-                p.do_outside_of_context(Context::InAsync, |p| {-                    if p.input().is(Token::Eq) {-                        p.assert_and_bump(Token::Eq);-                        p.parse_assignment_expr().map(Some)-                    } else {-                        Ok(None)-                    }-                })-            })?;+        self.do_inside_of_boundary_context(BoundaryContext::InClassField, |p| {+            with_grammar_context!(p, [+In, ?Yield, ?Await], |p| {+                // Instance field initializers do not inherit a static block's Await+                // restriction. All fields clear the Program probe's async context.+                let reset_context = if is_static {+                    BoundaryContext::empty()+                } else {+                    BoundaryContext::InStaticBlock+                };+                let value = p.without_async_arrow_param_await_collection(|p| {+                    // FieldDefinition still passes the enclosing Yield/Await flags in+                    // the specification. Follow V8, SpiderMonkey, and fixed JSC instead:+                    // an initializer has its own non-generator, non-async function+                    // boundary. Computed names were parsed before this boundary.+                    // The normative grammar is disputed; do not treat this reset as+                    // an unambiguous specification requirement.+                    // https://tc39.es/ecma262/#prod-FieldDefinition+                    // https://github.com/tc39/ecma262/issues/2437+                    // https://github.com/tc39/ecma262/issues/3333+                    with_grammar_context!(p, [+In, ~Yield, ~Await], |p| {+                        p.do_outside_of_boundary_context(reset_context, |p| {+                            p.with_syntax_context(SyntaxContext::Nested, |p| {+                                if p.input().is(Token::Eq) {+                                    p.assert_and_bump(Token::Eq);+                                    p.parse_assignment_expr().map(Some)+                                } else {+                                    Ok(None)+                                }+                            })+                        })+                    })+                })?; -            if declare && value.is_some() {-                p.emit_err(p.span(start), SyntaxError::TS1183);-            }+                // Definite assertions forbid initializers even outside ambient declarations.+                if definite && value.is_some() {+                    p.emit_err(p.span(start), SyntaxError::TS1263);+                } -            if p.syntax().flow() && p.ctx().contains(Context::InDeclare) && type_ann.is_none() {-                p.emit_err(p.span(start), SyntaxError::TS1003);-            }+                if declare && value.is_some() {+                    let allowed = p.syntax().typescript()+                        && readonly+                        && type_ann.is_none()+                        && value.as_deref().is_some_and(is_ts_ambient_initializer);+                    if !allowed {+                        p.emit_err(p.span(start), SyntaxError::TS1183);+                    }+                } -            if p.syntax().flow() && is_optional {-                p.emit_err(p.span(start), SyntaxError::TS1003);-            }+                if p.syntax().flow()+                    && p.type_ctx().contains(TypeContext::InDeclare)+                    && type_ann.is_none()+                {+                    p.emit_err(p.span(start), SyntaxError::TS1003);+                } -            let implicit_flow_separator = p.syntax().flow()-                && type_ann.is_some()-                && value.is_none()-                && p.input().cur().is_word();-            let had_explicit_separator =-                p.input().is(Token::Semi) || (p.syntax().flow() && p.input().is(Token::Comma));-            let ate_semi =-                p.eat_general_semi() || (p.syntax().flow() && p.input_mut().eat(Token::Comma));-            if !ate_semi && !implicit_flow_separator {-                p.emit_err(p.input().cur_span(), SyntaxError::TS1005);-            }-            if p.syntax().flow()-                && !had_explicit_separator-                && type_ann.is_some()-                && value.is_none()-                && p.input().had_line_break_before_cur()-                && p.input().is(Token::LBracket)-            {-                // `foo: T` followed by a computed key on the next line is only-                // ambiguous when the separator came from ASI. An explicit `;`-                // or Flow `,` should keep the next computed field valid.-                p.emit_err(p.input().cur_span(), SyntaxError::TS1005);-            }+                if p.syntax().flow() && is_optional {+                    p.emit_err(p.span(start), SyntaxError::TS1003);+                } -            if accessor_token.is_some() {-                return Ok(ClassMember::AutoAccessor(AutoAccessor {-                    span: p.span(start),-                    key,-                    value,-                    type_ann,-                    is_static,-                    decorators,-                    accessibility,-                    is_abstract,-                    is_override,-                    definite,-                }));-            }+                let implicit_flow_separator = p.syntax().flow()+                    && type_ann.is_some()+                    && value.is_none()+                    && p.input().cur().is_word();+                let had_explicit_separator =+                    p.input().is(Token::Semi) || (p.syntax().flow() && p.input().is(Token::Comma));+                let ate_semi =+                    p.eat_general_semi() || (p.syntax().flow() && p.input_mut().eat(Token::Comma));+                if !ate_semi && !implicit_flow_separator {+                    p.emit_err(p.input().cur_span(), SyntaxError::TS1005);+                }+                if p.syntax().flow()+                    && !had_explicit_separator+                    && type_ann.is_some()+                    && value.is_none()+                    && p.input().had_line_break_before_cur()+                    && p.input().is(Token::LBracket)+                {+                    // `foo: T` followed by a computed key on the next line is only+                    // ambiguous when the separator came from ASI. An explicit `;`+                    // or Flow `,` should keep the next computed field valid.+                    p.emit_err(p.input().cur_span(), SyntaxError::TS1005);+                } -            Ok(match key {-                Key::Private(key) => {-                    let span = p.span(start);-                    if accessibility.is_some() {-                        p.emit_err(span.with_hi(key.span_hi()), SyntaxError::TS18010);-                    }+                // Check both ordinary properties and auto-accessors before constructing the+                // AST.+                if is_abstract && value.is_some() {+                    p.emit_err(p.span(start), SyntaxError::TS1267);+                } -                    PrivateProp {+                if accessor_token.is_some() {+                    return Ok(ClassMember::AutoAccessor(AutoAccessor {                         span: p.span(start),                         key,                         value,-                        is_static,-                        decorators,-                        accessibility,-                        is_optional,-                        is_override,-                        readonly,                         type_ann,-                        definite,-                        ctxt: Default::default(),-                    }-                    .into()-                }-                Key::Public(key) => {-                    let span = p.span(start);-                    if is_abstract && value.is_some() {-                        p.emit_err(span, SyntaxError::TS1267)-                    }-                    ClassProp {-                        span,-                        key,-                        value,                         is_static,                         decorators,                         accessibility,                         is_abstract,-                        is_optional,                         is_override,-                        readonly,-                        declare,                         definite,-                        type_ann,-                    }-                    .into()+                    }));                 }-                #[cfg(swc_ast_unknown)]-                _ => unreachable!(),++                Ok(match key {+                    Key::Private(key) => {+                        let span = p.span(start);+                        if accessibility.is_some() {+                            p.emit_err(span.with_hi(key.span_hi()), SyntaxError::TS18010);+                        }++                        PrivateProp {+                            span: p.span(start),+                            key,+                            value,+                            is_static,+                            decorators,+                            accessibility,+                            is_optional,+                            is_override,+                            readonly,+                            type_ann,+                            definite,+                            ctxt: Default::default(),+                        }+                        .into()+                    }+                    Key::Public(key) => {+                        let span = p.span(start);+                        ClassProp {+                            span,+                            key,+                            value,+                            is_static,+                            decorators,+                            accessibility,+                            is_abstract,+                            is_optional,+                            is_override,+                            readonly,+                            declare,+                            definite,+                            type_ann,+                        }+                        .into()+                    }+                    #[cfg(swc_ast_unknown)]+                    _ => unreachable!(),+                })             })         })     }      fn parse_static_block(&mut self, start: BytePos) -> PResult<ClassMember> {-        let body = self.do_inside_of_context(-            Context::InStaticBlock-                .union(Context::InClassField)-                .union(Context::AllowUsingDecl),-            |p| p.parse_block(false),+        let body = self.do_inside_of_boundary_context(+            BoundaryContext::InStaticBlock | BoundaryContext::InClassField,+            |p| {+                p.do_inside_of_statement_context(StatementContext::AllowUsingDecl, |p| {+                    // Static blocks have StatementList[~Yield, +Await, ~Return].+                    // Await is recognized, but rejected by the static-block early error.+                    // https://tc39.es/ecma262/#prod-ClassStaticBlockStatementList+                    with_grammar_context!(p, [+In, ~Yield, +Await, ~Return], |p| {+                        p.with_syntax_context(SyntaxContext::Nested, |p| {+                            p.parse_block(false)+                        })+                    })+                })+            },         )?;          let span = self.span(start);@@ -1035,6 +1105,7 @@ impl<I: Tokens> Parser<I> {      fn parse_class_member_with_is_static(         &mut self,+        has_super_class: bool,         start: BytePos,         declare_token: Option<Span>,         accessibility: Option<Accessibility>,@@ -1094,7 +1165,7 @@ impl<I: Tokens> Parser<I> {                             self.input().prev_span(),                             SyntaxError::TS1243(atom!("override"), atom!("declare")),                         );-                    } else if !self.ctx().contains(Context::HasSuperClass) {+                    } else if !has_super_class {                         self.emit_err(self.input().prev_span(), SyntaxError::TS4112);                     }                     is_override = true;@@ -1145,7 +1216,10 @@ impl<I: Tokens> Parser<I> {             }             return self.parse_static_block(start);         }-        if self.input().is(Token::Static) && peek!(self).is_some_and(|cur| cur == Token::LBrace) {+        if self.input().is(Token::Static)+            && !self.input().has_escaped_keyword()+            && peek!(self).is_some_and(|cur| cur == Token::LBrace)+        {             // For "readonly", "abstract" and "override"             if let Some(span) = modifier_span {                 self.emit_err(span, SyntaxError::TS1184);@@ -1190,16 +1264,25 @@ impl<I: Tokens> Parser<I> {             }         } +        // Static methods named `constructor` are ordinary methods in JavaScript+        // and Flow. Preserve TypeScript's constructor classification so invalid+        // constructor modifiers still produce the existing diagnostics.+        let can_be_constructor =+            static_token.is_none() || (self.syntax().typescript() && !self.syntax().flow());+         if self.input_mut().eat(Token::Asterisk) {             // generator method             let key = self.parse_class_prop_name()?;+            // TypeScript permits the same optional marker on generator methods.+            let is_optional =+                self.input().syntax().typescript() && self.input_mut().eat(Token::QuestionMark);             if let Some(variance_span) = flow_variance_span {                 self.emit_err(variance_span, SyntaxError::TS1184);             }             if readonly.is_some() {                 self.emit_err(self.span(start), SyntaxError::ReadOnlyMethod);             }-            if is_constructor(&key) && (!self.syntax().flow() || static_token.is_none()) {+            if can_be_constructor && is_constructor(&key) {                 self.emit_err(self.span(start), SyntaxError::GeneratorConstructor);             } @@ -1213,7 +1296,7 @@ impl<I: Tokens> Parser<I> {                     accessibility,                     is_abstract,                     is_override,-                    is_optional: false,+                    is_optional,                     static_token,                     key,                     kind: MethodKind::Method,@@ -1269,8 +1352,7 @@ impl<I: Tokens> Parser<I> {                     self.emit_err(self.input().cur_span(), SyntaxError::TS1003);                 }             }-            let is_constructor =-                is_constructor(&key) && (!self.syntax().flow() || static_token.is_none());+            let is_constructor = can_be_constructor && is_constructor(&key);              if is_constructor {                 if self.syntax().typescript() && is_override {@@ -1299,45 +1381,56 @@ impl<I: Tokens> Parser<I> {                  let prev_allow_super_call = self.allow_super_call();                 self.set_allow_super_call(true);-                let ctor_sig_and_body =-                    (|| -> PResult<(Vec<ParamOrTsParamProp>, Option<FunctionBody>)> {-                        expect!(self, Token::LParen);-                        let params = self.parse_constructor_params()?;-                        expect!(self, Token::RParen);--                        if self.syntax().flow() {-                            for param in &params {-                                if let ParamOrTsParamProp::Param(Param {-                                    pat: Pat::Ident(ident),-                                    ..-                                }) = param-                                {-                                    if ident.id.sym == *"this" {-                                        self.emit_err(ident.id.span, SyntaxError::TS1003);+                // Ordinary methods, including constructors, use [~Yield, ~Await] for+                // UniqueFormalParameters and FunctionBody. Also clear enclosing field+                // and static-block contexts so their await/arguments restrictions do+                // not leak into constructor parameters or the body.+                let ctor_sig_and_body = self.do_outside_of_boundary_context(+                    BoundaryContext::InClassField | BoundaryContext::InStaticBlock,+                    |p| {+                        with_grammar_context!(p, [~Yield, ~Await], |p| {+                            expect!(p, Token::LParen);+                            let params = p.without_async_arrow_param_await_collection(+                                Self::parse_constructor_params,+                            )?;+                            expect!(p, Token::RParen);++                            if p.syntax().flow() {+                                for param in &params {+                                    if let ParamOrTsParamProp::Param(Param {+                                        pat: Pat::Ident(ident),+                                        ..+                                    }) = param+                                    {+                                        if ident.id.sym == *"this" {+                                            p.emit_err(ident.id.span, SyntaxError::TS1003);+                                        }                                     }                                 }                             }-                        } -                        if self.syntax().typescript() && self.input().is(Token::Colon) {-                            let start = self.cur_pos();-                            let type_ann = self.parse_ts_type_ann(true, start)?;+                            if p.syntax().typescript() && p.input().is(Token::Colon) {+                                let start = p.cur_pos();+                                let type_ann = p.parse_ts_type_ann(true, start)?; -                            // Flow allows return type annotations on constructors.-                            if !self.syntax().flow() {-                                self.emit_err(type_ann.type_ann.span(), SyntaxError::TS1093);+                                // Flow allows return type annotations on constructors.+                                if !p.syntax().flow() {+                                    p.emit_err(type_ann.type_ann.span(), SyntaxError::TS1093);+                                }                             }-                        } -                        let body = self.parse_fn_block_body(-                            false,-                            false,-                            false,-                            params.is_simple_parameter_list(),-                        )?;+                            let body = p.parse_fn_block_body(+                                FunctionKind::Function {+                                    is_async: false,+                                    is_generator: false,+                                },+                                params.is_simple_parameter_list(),+                            )?; -                        Ok((params, body))-                    })();+                            Ok((params, body))+                        })+                    },+                );                 self.set_allow_super_call(prev_allow_super_call);                 let (params, body) = ctor_sig_and_body?; @@ -1467,7 +1560,7 @@ impl<I: Tokens> Parser<I> {              let is_generator = self.input_mut().eat(Token::Asterisk);             let key = self.parse_class_prop_name()?;-            if is_constructor(&key) && (!self.syntax().flow() || static_token.is_none()) {+            if can_be_constructor && is_constructor(&key) {                 syntax_error!(self, key.span(), SyntaxError::AsyncConstructor)             }             if readonly.is_some() {@@ -1512,7 +1605,7 @@ impl<I: Tokens> Parser<I> {                 self.emit_err(key_span, SyntaxError::TS1003);             } -            if is_constructor(&key) && (!self.syntax().flow() || static_token.is_none()) {+            if can_be_constructor && is_constructor(&key) {                 self.emit_err(key_span, SyntaxError::ConstructorAccessor);             } @@ -1590,7 +1683,7 @@ impl<I: Tokens> Parser<I> {         unexpected!(self, "* for generator, private key, identifier or async")     } -    fn parse_class_member(&mut self) -> PResult<ClassMember> {+    fn parse_class_member(&mut self, has_super_class: bool) -> PResult<ClassMember> {         trace_cur!(self, parse_class_member);          let start = self.cur_pos();@@ -1662,8 +1755,12 @@ impl<I: Tokens> Parser<I> {          let static_token = {             let start = self.cur_pos();-            if self.input_mut().eat(Token::Static) {-                Some(self.span(start))+            // Escaped keywords are property names, never modifiers. In particular,+            // an escaped `static` followed by a line break must allow field ASI.+            if self.input().is(Token::Static) && !self.input().has_escaped_keyword() {+                self.bump();+                let span = self.span(start);+                Some(span)             } else {                 None             }@@ -1788,12 +1885,11 @@ impl<I: Tokens> Parser<I> {                         false,                     );                 }-            } else {-                // TODO: error if static contains escape             }         }          self.parse_class_member_with_is_static(+            has_super_class,             start,             declare_token,             accessibility,@@ -1803,7 +1899,7 @@ impl<I: Tokens> Parser<I> {         )     } -    fn parse_class_body(&mut self) -> PResult<Vec<ClassMember>> {+    fn parse_class_body(&mut self, has_super_class: bool) -> PResult<Vec<ClassMember>> {         let mut elems = Vec::with_capacity(32);         let mut has_constructor_with_body = false;         while !self.input().is(Token::RBrace) {@@ -1813,10 +1909,12 @@ impl<I: Tokens> Parser<I> {                 elems.push(ClassMember::Empty(EmptyStmt { span }));                 continue;             }-            let elem =-                self.do_inside_of_context(Context::AllowDirectSuper, Self::parse_class_member)?;+            let elem = self+                .do_inside_of_boundary_context(BoundaryContext::AllowDirectSuper, |p| {+                    p.parse_class_member(has_super_class)+                })?; -            if !self.ctx().contains(Context::InDeclare) {+            if !self.type_ctx().contains(TypeContext::InDeclare) {                 if let ClassMember::Constructor(Constructor {                     body: Some(..),                     span,@@ -1832,7 +1930,7 @@ impl<I: Tokens> Parser<I> {             elems.push(elem);              // Flow `declare class` allows `,` as a class member separator.-            if self.syntax().flow() && self.ctx().contains(Context::InDeclare) {+            if self.syntax().flow() && self.type_ctx().contains(TypeContext::InDeclare) {                 self.input_mut().eat(Token::Comma);             }         }@@ -1897,9 +1995,10 @@ impl<I: Tokens> Parser<I> {     where         T: OutputType,     {-        let (ident, mut class) = self.do_inside_of_context(Context::InClass, |p| {-            p.parse_class_inner(start, class_start, decorators, T::IS_IDENT_REQUIRED)-        })?;+        let (ident, mut class) = self+            .do_inside_of_boundary_context(BoundaryContext::InClass, |p| {+                p.parse_class_inner(start, class_start, decorators, T::IS_IDENT_REQUIRED)+            })?;          if is_abstract {             class.is_abstract = true@@ -1935,6 +2034,7 @@ impl<I: Tokens> Parser<I> {         decorators: Vec<Decorator>,         is_ident_required: bool,     ) -> PResult<(Option<Ident>, Box<Class>)> {+        let outer_ctx = self.ctx();         self.strict_mode(|p| {             expect!(p, Token::Class); @@ -2031,12 +2131,11 @@ impl<I: Tokens> Parser<I> {              expect!(p, Token::LBrace); -            let body = if super_class.is_some() {-                p.do_inside_of_context(Context::HasSuperClass, Self::parse_class_body)?-            } else {-                p.do_outside_of_context(Context::HasSuperClass, Self::parse_class_body)?-            };+            let body = p.parse_class_body(super_class.is_some())?; +            // Consuming `}` also lexes the next token, which belongs to the enclosing+            // scope and must not inherit the class's strict mode.+            p.set_ctx(outer_ctx);             if p.input().cur() == Token::Eof {                 let eof_text = p.input_mut().dump_cur();                 p.emit_err(

deps/swc/crates/swc_ecma_parser/src/parser/context.rs

new file mode 100644@@ -0,0 +1,136 @@+/// Function-body grammar and lexical-scope behavior. Arrows cannot be+/// generators and inherit the enclosing non-arrow function scope.+#[derive(Clone, Copy)]+pub(super) enum FunctionKind {+    Function { is_async: bool, is_generator: bool },+    Arrow { is_async: bool },+}++impl FunctionKind {+    pub(super) fn is_async(self) -> bool {+        match self {+            Self::Function { is_async, .. } | Self::Arrow { is_async } => is_async,+        }+    }++    pub(super) fn is_generator(self) -> bool {+        matches!(+            self,+            Self::Function {+                is_generator: true,+                ..+            }+        )+    }++    pub(super) fn is_arrow(self) -> bool {+        matches!(self, Self::Arrow { .. })+    }+}++/// The grammar production accepted at a statement entry point.+/// Unlike `StatementContext`, this is local to the production, not inherited+/// control-flow permission. Import/export handling remains with the caller.+#[derive(Clone, Copy)]+pub(super) enum StatementGrammar {+    Statement,+    StatementListItem,+    ModuleItem,+}++impl StatementGrammar {+    pub(super) fn permits_declaration(self) -> bool {+        matches!(self, Self::StatementListItem | Self::ModuleItem)+    }+}++/// A block item's AST type selects its production before entering the body+/// loop. The loop does not carry a runtime grammar selector for every item.+pub(super) trait BlockBodyItem: From<swc_ecma_ast::Stmt> {+    const GRAMMAR: StatementGrammar;+}++impl BlockBodyItem for swc_ecma_ast::Stmt {+    const GRAMMAR: StatementGrammar = StatementGrammar::StatementListItem;+}++impl BlockBodyItem for swc_ecma_ast::ModuleItem {+    const GRAMMAR: StatementGrammar = StatementGrammar::ModuleItem;+}++bitflags::bitflags! {+    /// The active values of ECMAScript grammatical parameters.+    ///+    /// These flags model the specification's `[In]`, `[Yield]`, `[Await]`, and `[Return]`+    /// parameters. They belong to the parser rather than the lexer because+    /// productions derive and restore them independently of tokenization.+    #[derive(Debug, Clone, Copy, Default)]+    pub(super) struct GrammarContext: u8 {+        /// The grammar's `[In]` parameter.+        const In = 1 << 0;+        /// The grammar's `[Yield]` parameter.+        const Yield = 1 << 1;+        /// The grammar's `[Await]` parameter.+        const Await = 1 << 2;+        /// The grammar's `[Return]` parameter.+        const Return = 1 << 3;+    }+}++bitflags::bitflags! {+    /// State inherited across function, class, and program boundaries.+    #[derive(Debug, Clone, Copy, Default)]+    pub(super) struct BoundaryContext: u8 {+        const CanBeModule = 1 << 0;+        const TopLevel = 1 << 1;+        const InsideNonArrowFunctionScope = 1 << 2;+        const InParameters = 1 << 3;+        const InClass = 1 << 4;+        const InClassField = 1 << 5;+        const InStaticBlock = 1 << 6;+        const AllowDirectSuper = 1 << 7;+    }+}++/// The nearest syntactic region relevant to parser early errors.+///+/// Unlike grammatical parameters, this records where an expression occurs.+/// A nested function body replaces a parameter or class-initializer region;+/// ordinary expressions and statements inherit it.+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]+pub(super) enum SyntaxContext {+    #[default]+    Program,+    Parameters,+    FunctionBody,+    /// A class initializer, static block, or TypeScript module body. These+    /// regions neither classify the program nor use function/parameter errors.+    Nested,+}++bitflags::bitflags! {+    /// State derived from the enclosing statement grammar.+    #[derive(Debug, Clone, Copy, Default)]+    pub(super) struct StatementContext: u8 {+        const IsContinueAllowed = 1 << 0;+        const IsBreakAllowed = 1 << 1;+        const IgnoreElseClause = 1 << 2;+        const AllowUsingDecl = 1 << 3;+    }+}++bitflags::bitflags! {+    /// Parser-only TypeScript, Flow, and cover-grammar state.+    #[derive(Debug, Clone, Copy, Default)]+    pub(super) struct TypeContext: u8 {+        const InDeclare = 1 << 0;+        /// A typed arrow must leave a colon for the enclosing conditional+        /// consequent before its speculative parse can commit.+        const WillExpectColonForCond = 1 << 1;+        const DisallowConditionalTypes = 1 << 2;+        const TsModuleBlock = 1 << 3;+        const DisallowFlowAnonFnType = 1 << 4;+        /// Type grammar, independent of how angle operators are tokenized.+        const InType = 1 << 5;+    }+}

deps/swc/crates/swc_ecma_parser/src/parser/expr.rs

@@ -97,7 +97,7 @@ impl<I: Tokens> Parser<I> {             let start = self.input().prev_span().lo;             let spread_span = self.span(start);             let spread = Some(spread_span);-            self.allow_in_expr(Self::parse_assignment_expr)+            with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)                 .map_err(|err| {                     Error::new(                         err.span(),@@ -153,7 +153,7 @@ impl<I: Tokens> Parser<I> {             && (peek!(self).is_some_and(|peek| peek.is_word() || peek == Token::JSXName))         {             let start = self.input().cur_span();-            let res = self.do_outside_of_context(Context::WillExpectColonForCond, |p| {+            let res = self.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {                 p.try_parse_ts(|p| {                     let type_parameters = p.parse_ts_type_params(false, true)?; @@ -213,7 +213,7 @@ impl<I: Tokens> Parser<I> {             }         } -        if cur == Token::Yield && self.ctx().contains(Context::InGenerator) {+        if cur == Token::Yield && self.includes_yield_expr() {             return self.parse_yield_expr();         } @@ -275,7 +275,7 @@ impl<I: Tokens> Parser<I> {                 let start = self.input().cur_pos();                 self.bump(); // consume `<`                 return if self.input_mut().eat(Token::Const) {-                    self.expect(Token::Gt)?;+                    self.expect_ts_type_gt()?;                     let expr = self.parse_unary_expr()?;                     Ok(TsConstAssertion {                         span: self.span(start),@@ -392,15 +392,16 @@ impl<I: Tokens> Parser<I> {                 }                 .into());             } else if cur == Token::Await {-                let parses_await = self.ctx().intersects(-                    Context::InAsync-                        .union(Context::Module)-                        .union(Context::InStaticBlock),-                ) || self.is_unambiguous_module()+                let parses_await = self.includes_await_expr()+                    || self.ctx().contains(Context::Module)+                    || self+                        .boundary_ctx()+                        .contains(BoundaryContext::InStaticBlock)+                    || self.is_unambiguous_module()                     // Direct node parsers, such as `parse_file_as_expr`, opt into                     // module-capable grammar without parsing an entire Program.                     || (self.program_parse_mode == ProgramParseMode::None-                        && self.ctx().contains(Context::CanBeModule));+                        && self.boundary_ctx().contains(BoundaryContext::CanBeModule));                  if parses_await {                     return self.parse_await_expr(None);@@ -456,8 +457,10 @@ impl<I: Tokens> Parser<I> {                 }             }             Token::LBracket => {-                return self-                    .do_outside_of_context(Context::WillExpectColonForCond, Self::parse_array_lit)+                return self.do_outside_of_type_context(+                    TypeContext::WillExpectColonForCond,+                    Self::parse_array_lit,+                )             }             Token::LBrace => {                 return self.parse_object_expr().map(Box::new);@@ -483,7 +486,9 @@ impl<I: Tokens> Parser<I> {             Token::TemplateHead => {                 // parse template literal                 return Ok(self-                    .do_outside_of_context(Context::WillExpectColonForCond, |p| p.parse_tpl(false))?+                    .do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                        p.parse_tpl(false)+                    })?                     .into());             }             _ => {}@@ -524,8 +529,7 @@ impl<I: Tokens> Parser<I> {             cur == Token::Lt || cur == Token::LShift         } {             self.try_parse_ts(|p| {-                let type_args = p.parse_ts_type_args()?;-                p.assert_and_bump(Token::Gt);+                let type_args = p.parse_ts_type_args_in_expr()?;                 if p.input().is(Token::LParen) {                     Ok(Some(type_args))                 } else {@@ -633,11 +637,23 @@ impl<I: Tokens> Parser<I> {                 continue;             } -            elems.push(self.allow_in_expr(|p| p.parse_expr_or_spread()).map(Some)?);+            elems.push(+                with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr_or_spread())+                    .map(Some)?,+            );              if !self.input().is(Token::RBracket) {                 expect!(self, Token::Comma);-                if self.input().is(Token::RBracket) {+                if self.input().is(Token::RBracket)+                    && matches!(+                        elems.last(),+                        Some(Some(ExprOrSpread {+                            spread: Some(_),+                            ..+                        }))+                    )+                {+                    // Only a spread can become a rest element with an invalid trailing comma.                     let prev_span = self.input().prev_span();                     self.state_mut().trailing_commas.insert(start, prev_span);                 }@@ -656,15 +672,16 @@ impl<I: Tokens> Parser<I> {     }      fn parse_yield_expr(&mut self) -> PResult<Box<Expr>> {+        self.check_current_token_escape()?;         let start = self.input().cur_pos();         self.assert_and_bump(Token::Yield);-        debug_assert!(self.ctx().contains(Context::InGenerator));+        debug_assert!(self.includes_yield_expr());          // Spec says         // YieldExpression cannot be used within the FormalParameters of a generator         // function because any expressions that are part of FormalParameters are         // evaluated before the resulting generator object is in a resumable state.-        if self.ctx().contains(Context::InParameters) && !self.ctx().contains(Context::InFunction) {+        if self.syntax_context == SyntaxContext::Parameters {             syntax_error!(self, self.input().prev_span(), SyntaxError::YieldParamInGen)         } @@ -720,7 +737,7 @@ impl<I: Tokens> Parser<I> {         let mut quasis = vec![cur_elem];          while !is_tail {-            exprs.push(self.allow_in_expr(|p| p.parse_expr())?);+            exprs.push(with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?);             let elem = self.parse_tpl_element(is_tagged_tpl)?;             is_tail = elem.tail;             quasis.push(elem);@@ -1058,38 +1075,46 @@ impl<I: Tokens> Parser<I> {     pub(crate) fn parse_args(&mut self, is_dynamic_import: bool) -> PResult<Vec<ExprOrSpread>> {         trace_cur!(self, parse_args); -        self.do_outside_of_context(Context::WillExpectColonForCond, |p| {-            let start = p.cur_pos();-            expect!(p, Token::LParen);+        // Arguments use +In for the whole list, independently of the enclosing+        // expression.+        with_grammar_context!(self, [+In, ?Yield, ?Await], |p| {+            p.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                let start = p.cur_pos();+                expect!(p, Token::LParen); -            if p.input().is(Token::RParen) {-                expect!(p, Token::RParen);-                return Ok(Vec::new());-            }+                if p.input().is(Token::RParen) {+                    expect!(p, Token::RParen);+                    return Ok(Vec::new());+                } -            let mut first = true;-            let mut expr_or_spreads = Vec::with_capacity(2);+                let mut first = true;+                let mut expr_or_spreads = Vec::with_capacity(2); -            while !p.input().is(Token::RParen) {-                if first {-                    first = false;-                } else {-                    expect!(p, Token::Comma);-                    // Handle trailing comma.-                    if p.input().is(Token::RParen) {-                        if is_dynamic_import && !p.input().syntax().import_attributes() {-                            syntax_error!(p, p.span(start), SyntaxError::TrailingCommaInsideImport)-                        }+                while !p.input().is(Token::RParen) {+                    if first {+                        first = false;+                    } else {+                        expect!(p, Token::Comma);+                        // Handle trailing comma.+                        if p.input().is(Token::RParen) {+                            if is_dynamic_import && !p.input().syntax().import_attributes() {+                                syntax_error!(+                                    p,+                                    p.span(start),+                                    SyntaxError::TrailingCommaInsideImport+                                )+                            } -                        break;+                            break;+                        }                     }-                } -                expr_or_spreads.push(p.allow_in_expr(|p| p.parse_expr_or_spread())?);-            }+                    expr_or_spreads.push(p.parse_expr_or_spread()?);+                } -            expect!(p, Token::RParen);-            Ok(expr_or_spreads)+                expect!(p, Token::RParen);+                Ok(expr_or_spreads)+            })         })     } @@ -1160,21 +1185,15 @@ impl<I: Tokens> Parser<I> {          if self.input_mut().eat(Token::QuestionMark) {             let start = test.span_lo();-            let cons = self.do_inside_of_context(-                Context::InCondExpr-                    .union(Context::WillExpectColonForCond)-                    .union(Context::IncludeInExpr),-                Self::parse_assignment_expr,+            let cons = self.do_inside_of_type_context(+                TypeContext::WillExpectColonForCond,+                |p| with_grammar_context!(p, [+In, ?Yield, ?Await], Self::parse_assignment_expr),             )?;              expect!(self, Token::Colon); -            let alt = self.do_inside_of_context(Context::InCondExpr, |p| {-                p.do_outside_of_context(-                    Context::WillExpectColonForCond,-                    Self::parse_assignment_expr,-                )-            })?;+            // The alternate still belongs to any enclosing consequent.+            let alt = self.parse_assignment_expr()?;              let span = Span::new_with_checked(start, alt.span_hi());             Ok(CondExpr {@@ -1307,72 +1326,69 @@ impl<I: Tokens> Parser<I> {                 // on tagged template expressions. If any of them fail, walk it back and                 // continue. -                let result = self.do_inside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {-                    p.try_parse_ts(|p| {-                        if !no_call && p.at_possible_async(&callee) {-                            // Almost certainly this is a generic async function `async <T>() =>-                            // ... But it might be a call with a-                            // type argument `async<T>();`-                            let async_arrow_fn = p.try_parse_ts_generic_async_arrow_fn(start)?;-                            if let Some(async_arrow_fn) = async_arrow_fn {-                                return Ok(Some((async_arrow_fn.into(), true)));-                            }+                let result = self.try_parse_ts(|p| {+                    if !no_call && p.at_possible_async(&callee) {+                        // Almost certainly this is a generic async function `async <T>() =>+                        // ... But it might be a call with a+                        // type argument `async<T>();`+                        let async_arrow_fn = p.try_parse_ts_generic_async_arrow_fn(start)?;+                        if let Some(async_arrow_fn) = async_arrow_fn {+                            return Ok(Some((async_arrow_fn.into(), true)));                         }+                    } -                        let type_args = p.parse_ts_type_args()?;-                        p.assert_and_bump(Token::Gt);-                        let cur = p.input().cur();+                    let type_args = p.parse_ts_type_args_in_expr()?;+                    let cur = p.input().cur(); -                        if !no_call && cur == Token::LParen {-                            // possibleAsync always false here, because we would have handled it-                            // above. (won't be any undefined arguments)-                            let args = p.parse_args(false)?;+                    if !no_call && cur == Token::LParen {+                        // possibleAsync always false here, because we would have handled it+                        // above. (won't be any undefined arguments)+                        let args = p.parse_args(false)?; -                            let expr = if callee.is_opt_chain() {-                                Expr::OptChain(OptChainExpr {-                                    span: p.span(start),-                                    base: Box::new(OptChainBase::Call(OptCall {-                                        span: p.span(start),-                                        callee: callee.take(),-                                        type_args: Some(type_args),-                                        args,-                                        ..Default::default()-                                    })),-                                    optional: false,-                                })-                            } else {-                                Expr::Call(CallExpr {+                        let expr = if callee.is_opt_chain() {+                            Expr::OptChain(OptChainExpr {+                                span: p.span(start),+                                base: Box::new(OptChainBase::Call(OptCall {                                     span: p.span(start),-                                    callee: Callee::Expr(callee.take()),+                                    callee: callee.take(),                                     type_args: Some(type_args),                                     args,                                     ..Default::default()-                                })-                            };--                            Ok(Some((Box::new(expr), true)))-                        } else if matches!(-                            cur,-                            Token::NoSubstitutionTemplateLiteral-                                | Token::TemplateHead-                                | Token::BackQuote-                        ) {-                            p.parse_tagged_tpl(callee.take(), Some(type_args))-                                .map(|expr| (expr.into(), true))-                                .map(Some)-                        } else if matches!(cur, Token::Eq | Token::As | Token::Satisfies) {-                            let expr = Expr::TsInstantiation(TsInstantiation {-                                span: p.span(start),-                                expr: callee.take(),-                                type_args,-                            });-                            Ok(Some((Box::new(expr), false)))-                        } else if no_call {-                            unexpected!(p, "`")+                                })),+                                optional: false,+                            })                         } else {-                            unexpected!(p, "( or `")-                        }-                    })+                            Expr::Call(CallExpr {+                                span: p.span(start),+                                callee: Callee::Expr(callee.take()),+                                type_args: Some(type_args),+                                args,+                                ..Default::default()+                            })+                        };++                        Ok(Some((Box::new(expr), true)))+                    } else if matches!(+                        cur,+                        Token::NoSubstitutionTemplateLiteral+                            | Token::TemplateHead+                            | Token::BackQuote+                    ) {+                        p.parse_tagged_tpl(callee.take(), Some(type_args))+                            .map(|expr| (expr.into(), true))+                            .map(Some)+                    } else if matches!(cur, Token::Eq | Token::As | Token::Satisfies) {+                        let expr = Expr::TsInstantiation(TsInstantiation {+                            span: p.span(start),+                            expr: callee.take(),+                            type_args,+                        });+                        Ok(Some((Box::new(expr), false)))+                    } else if no_call {+                        unexpected!(p, "`")+                    } else {+                        unexpected!(p, "( or `")+                    }                 });                  if let Some(expr) = result {@@ -1400,7 +1416,7 @@ impl<I: Tokens> Parser<I> {         // $obj[name()]         if !NO_COMPUTED && self.input_mut().eat(Token::LBracket) {             let bracket_lo = self.input().prev_span().lo;-            let prop = self.allow_in_expr(|p| p.parse_expr())?;+            let prop = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;             expect!(self, Token::RBracket);             let span = Span::new_with_checked(callee.span_lo(), self.input().last_pos());             debug_assert_eq!(callee.span_lo(), span.lo());@@ -1445,9 +1461,7 @@ impl<I: Tokens> Parser<I> {         }          let type_args = if syntax.typescript() && self.input().is(Token::Lt) && question_dot {-            let ret = self.parse_ts_type_args()?;-            self.assert_and_bump(Token::Gt);-            Some(ret)+            Some(self.parse_ts_type_args_in_expr()?)         } else {             None         };@@ -1488,7 +1502,7 @@ impl<I: Tokens> Parser<I> {             })?;             if syntax.flow()                 && matches!(prop, MemberProp::PrivateName(..))-                && !self.ctx().contains(Context::InClass)+                && !self.boundary_ctx().contains(BoundaryContext::InClass)             {                 self.emit_err(self.input().prev_span(), SyntaxError::TS1003);             }@@ -1548,9 +1562,10 @@ impl<I: Tokens> Parser<I> {             cur,             Token::TemplateHead | Token::NoSubstitutionTemplateLiteral | Token::BackQuote         ) {-            let tpl = self.do_outside_of_context(Context::WillExpectColonForCond, |p| {-                p.parse_tagged_tpl(expr, None)-            })?;+            let tpl = self+                .do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                    p.parse_tagged_tpl(expr, None)+                })?;             return Ok((tpl.into(), true));         } @@ -1589,7 +1604,7 @@ impl<I: Tokens> Parser<I> {             })?;             if syntax.flow()                 && matches!(prop, MemberProp::PrivateName(..))-                && !self.ctx().contains(Context::InClass)+                && !self.boundary_ctx().contains(BoundaryContext::InClass)             {                 self.emit_err(self.input().prev_span(), SyntaxError::TS1003);             }@@ -1620,7 +1635,7 @@ impl<I: Tokens> Parser<I> {         if !NO_COMPUTED && cur == Token::LBracket {             self.bump();             let bracket_lo = self.input().prev_span().lo;-            let prop = self.allow_in_expr(|p| p.parse_expr())?;+            let prop = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;             expect!(self, Token::RBracket);             let span = Span::new_with_checked(callee.span_lo(), self.input().last_pos());             debug_assert_eq!(callee.span_lo(), span.lo());@@ -1683,7 +1698,7 @@ impl<I: Tokens> Parser<I> {             })?;             if syntax.flow()                 && matches!(prop, MemberProp::PrivateName(..))-                && !self.ctx().contains(Context::InClass)+                && !self.boundary_ctx().contains(BoundaryContext::InClass)             {                 self.emit_err(self.input().prev_span(), SyntaxError::TS1003);             }@@ -1710,9 +1725,10 @@ impl<I: Tokens> Parser<I> {             cur,             Token::TemplateHead | Token::NoSubstitutionTemplateLiteral | Token::BackQuote         ) {-            let tpl = self.do_outside_of_context(Context::WillExpectColonForCond, |p| {-                p.parse_tagged_tpl(callee, None)-            })?;+            let tpl = self+                .do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                    p.parse_tagged_tpl(callee, None)+                })?;             return Ok((tpl.into(), true));         } @@ -1735,7 +1751,7 @@ impl<I: Tokens> Parser<I> {             Token::LBracket => {                 self.bump();                 let bracket_lo = self.input().prev_span().lo;-                let prop = self.allow_in_expr(|p| p.parse_expr())?;+                let prop = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;                 expect!(self, Token::RBracket);                 let span = Span::new_with_checked(lhs.span_lo(), self.input().last_pos());                 debug_assert_eq!(lhs.span_lo(), span.lo());@@ -1744,7 +1760,9 @@ impl<I: Tokens> Parser<I> {                     expr: prop,                 }; -                if !self.ctx().contains(Context::AllowDirectSuper)+                if !self+                    .boundary_ctx()+                    .contains(BoundaryContext::AllowDirectSuper)                     && !self.input().syntax().allow_super_outside_method()                 {                     syntax_error!(self, lhs.span, SyntaxError::InvalidSuper)@@ -1779,7 +1797,9 @@ impl<I: Tokens> Parser<I> {                 debug_assert_eq!(lhs.span_lo(), span.lo());                 debug_assert_eq!(prop.span_hi(), span.hi()); -                if !self.ctx().contains(Context::AllowDirectSuper)+                if !self+                    .boundary_ctx()+                    .contains(BoundaryContext::AllowDirectSuper)                     && !self.input().syntax().allow_super_outside_method()                 {                     syntax_error!(self, lhs.span, SyntaxError::InvalidSuper);@@ -1864,7 +1884,7 @@ impl<I: Tokens> Parser<I> {             match &*ident.sym {                 "meta" => {                     let span = self.span(start);-                    if !self.ctx().contains(Context::CanBeModule) {+                    if !self.boundary_ctx().contains(BoundaryContext::CanBeModule) {                         self.emit_err(span, SyntaxError::ImportMetaInScript);                     }                     let expr = MetaPropExpr {@@ -1900,16 +1920,6 @@ impl<I: Tokens> Parser<I> {         tracing::instrument(level = "debug", skip_all)     )]     fn parse_member_expr_or_new_expr(&mut self, is_new_expr: bool) -> PResult<Box<Expr>> {-        if self.ctx().contains(Context::InType) {-            self.do_inside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {-                p.parse_member_expr_or_new_expr_inner(is_new_expr)-            })-        } else {-            self.parse_member_expr_or_new_expr_inner(is_new_expr)-        }-    }--    fn parse_member_expr_or_new_expr_inner(&mut self, is_new_expr: bool) -> PResult<Box<Expr>> {         trace_cur!(self, parse_member_expr_or_new_expr);          let cur = self.input().cur();@@ -1925,10 +1935,10 @@ impl<I: Tokens> Parser<I> {                     }                     .into(); -                    let ctx = self.ctx();-                    if !ctx.contains(Context::InsideNonArrowFunctionScope)-                        && !ctx.contains(Context::InParameters)-                        && !ctx.contains(Context::InClass)+                    let boundary_context = self.boundary_ctx();+                    if !boundary_context.contains(BoundaryContext::InsideNonArrowFunctionScope)+                        && !boundary_context.contains(BoundaryContext::InParameters)+                        && !boundary_context.contains(BoundaryContext::InClass)                     {                         self.emit_err(span, SyntaxError::InvalidNewTarget);                     }@@ -1975,11 +1985,7 @@ impl<I: Tokens> Parser<I> {                 cur == Token::Lt || cur == Token::LShift             } {                 self.try_parse_ts(|p| {-                    let args = p.do_outside_of_context(-                        Context::ShouldNotLexLtOrGtAsType,-                        Self::parse_ts_type_args,-                    )?;-                    p.assert_and_bump(Token::Gt);+                    let args = p.parse_ts_type_args_in_expr()?;                     if !p.input().is(Token::LParen) {                         let span = p.input().cur_span();                         let cur = p.input_mut().dump_cur();@@ -2081,7 +2087,7 @@ impl<I: Tokens> Parser<I> {                 if cur == Token::Error {                     let err = self.input_mut().expect_error_token_and_bump();                     return Err(err);-                } else if (cur == Token::In && self.ctx().contains(Context::IncludeInExpr))+                } else if (cur == Token::In && self.includes_in_expr())                     || cur == Token::InstanceOf                     || cur.is_bin_op()                 {@@ -2134,7 +2140,7 @@ impl<I: Tokens> Parser<I> {         // Return left on eof         let cur = self.input().cur(); -        let op = if cur == Token::In && self.ctx().contains(Context::IncludeInExpr) {+        let op = if cur == Token::In && self.includes_in_expr() {             op!("in")         } else if cur == Token::InstanceOf {             op!("instanceof")@@ -2312,6 +2318,9 @@ impl<I: Tokens> Parser<I> {         &mut self,         start_of_await_token: Option<BytePos>,     ) -> PResult<Box<Expr>> {+        if start_of_await_token.is_none() {+            self.check_current_token_escape()?;+        }         let start = start_of_await_token.unwrap_or_else(|| self.cur_pos());          if start_of_await_token.is_none() {@@ -2328,16 +2337,18 @@ impl<I: Tokens> Parser<I> {         }          let ctx = self.ctx();+        let boundary_context = self.boundary_ctx(); -        if !ctx.contains(Context::InFunction)-            && ctx.intersects(Context::InClassField.union(Context::InStaticBlock))-        {+        let forbidden_in_initializer = self.is_await_forbidden_in_initializer();+        if forbidden_in_initializer {             self.emit_err(await_token, SyntaxError::ExpectedIdent);         }          let span = self.span(start); -        if !ctx.contains(Context::InAsync)+        // A forbidden await with no operand recovers as an identifier. Its early+        // error was already emitted, even when this region has the Await flag.+        if (!self.includes_await_expr() || forbidden_in_initializer)             && (self.is_general_semi() || {                 let cur = self.input().cur();                 matches!(@@ -2346,7 +2357,9 @@ impl<I: Tokens> Parser<I> {                 )             })         {-            if ctx.intersects(Context::Module.union(Context::CanBeModule)) {+            if ctx.contains(Context::Module)+                || boundary_context.contains(BoundaryContext::CanBeModule)+            {                 self.emit_module_mode_err(span, SyntaxError::InvalidIdentInAsync);             } @@ -2356,11 +2369,11 @@ impl<I: Tokens> Parser<I> {         let ambiguous_script_different_ast =             self.can_classify_module() && self.is_ambiguous_await_prefix(); -        if ctx.contains(Context::InFunction) && !ctx.contains(Context::InAsync) {+        if self.syntax_context == SyntaxContext::FunctionBody && !self.includes_await_expr() {             self.emit_err(await_token, SyntaxError::AwaitInFunction);         } -        if ctx.contains(Context::InParameters) && !ctx.contains(Context::InFunction) {+        if self.syntax_context == SyntaxContext::Parameters {             self.emit_err(span, SyntaxError::AwaitParamInAsync);         } @@ -2403,7 +2416,7 @@ impl<I: Tokens> Parser<I> {             | Token::Bang             | Token::Lt => true,             Token::Of | Token::InstanceOf if !is_escaped => true,-            Token::In if !is_escaped => self.ctx().contains(Context::IncludeInExpr),+            Token::In if !is_escaped => self.includes_in_expr(),             Token::As | Token::Satisfies if !is_escaped => self.input().syntax().typescript(),             Token::Regex => {                 unreachable!("regular expressions are initially scanned as '/' or '/='")@@ -2422,8 +2435,8 @@ impl<I: Tokens> Parser<I> {         tracing::instrument(level = "debug", skip_all)     )]     fn parse_args_or_pats(&mut self) -> PResult<(Vec<AssignTargetOrSpread>, Option<Span>)> {-        self.do_outside_of_context(-            Context::WillExpectColonForCond,+        self.do_outside_of_type_context(+            TypeContext::WillExpectColonForCond,             Self::parse_args_or_pats_inner,         )     }@@ -2478,7 +2491,7 @@ impl<I: Tokens> Parser<I> {                      ExprOrSpread { spread, expr }                 } else {-                    self.allow_in_expr(|p| p.parse_expr_or_spread())?+                    with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr_or_spread())?                 }             }; @@ -2509,20 +2522,13 @@ impl<I: Tokens> Parser<I> {                         expect!(self, Token::QuestionMark);                         let test = arg.expr; -                        let cons = self.do_inside_of_context(-                            Context::InCondExpr-                                .union(Context::WillExpectColonForCond)-                                .union(Context::IncludeInExpr),-                            Self::parse_assignment_expr,+                        let cons = self.do_inside_of_type_context(+                            TypeContext::WillExpectColonForCond,+                            |p| with_grammar_context!(p, [+In, ?Yield, ?Await], Self::parse_assignment_expr),                         )?;                         expect!(self, Token::Colon); -                        let alt = self.do_inside_of_context(Context::InCondExpr, |p| {-                            p.do_outside_of_context(-                                Context::WillExpectColonForCond,-                                Self::parse_assignment_expr,-                            )-                        })?;+                        let alt = self.parse_assignment_expr()?;                          arg = ExprOrSpread {                             spread: None,@@ -2693,9 +2699,7 @@ impl<I: Tokens> Parser<I> {                 self.record_await_in_arrow_params(&params);                  let body: Box<ArrowFunctionBody> = self.parse_fn_block_or_expr_body(-                    false,-                    false,-                    true,+                    FunctionKind::Arrow { is_async: false },                     params.is_simple_parameter_list(),                 )?;                 let span = self.span(start);@@ -2752,9 +2756,10 @@ impl<I: Tokens> Parser<I> {             self.state_mut().collect_async_arrow_param_await = true;             (was_collecting, previous_pending)         });-        let paren_result = self.do_outside_of_context(Context::WillExpectColonForCond, |p| {-            p.allow_in_expr(Self::parse_args_or_pats)-        });+        let paren_result = self.do_outside_of_type_context(+            TypeContext::WillExpectColonForCond,+            |p| with_grammar_context!(p, [+In, ?Yield, ?Await], Self::parse_args_or_pats),+        );         let pending_await = collection_checkpoint.map(|(was_collecting, previous_pending)| {             let pending = self.state_mut().pending_async_arrow_param_await.take();             self.state_mut().collect_async_arrow_param_await = was_collecting;@@ -2767,17 +2772,16 @@ impl<I: Tokens> Parser<I> {             .iter()             .any(|item| matches!(item, AssignTargetOrSpread::Pat(..))); -        let will_expect_colon_for_cond = self.ctx().contains(Context::WillExpectColonForCond);-        // This is slow path. We handle arrow in conditional expression.-        if self.syntax().typescript()-            && self.ctx().contains(Context::InCondExpr)-            && self.input().is(Token::Colon)+        let will_expect_colon_for_cond = self+            .type_ctx()+            .contains(TypeContext::WillExpectColonForCond);+        // Only a conditional consequent needs to speculate through the body+        // to distinguish a return annotation from the conditional's colon.+        if self.syntax().typescript() && will_expect_colon_for_cond && self.input().is(Token::Colon)         {-            // TODO: Remove clone-            let items_ref = &paren_items;-            if let Some(expr) = self.try_parse_ts(|p| {+            if let Some((params, return_type, body)) = self.try_parse_ts(|p| {                 let return_type = if p.input().syntax().flow() {-                    p.do_inside_of_context(Context::DisallowFlowAnonFnType, |p| {+                    p.do_inside_of_type_context(TypeContext::DisallowFlowAnonFnType, |p| {                         p.parse_ts_type_or_type_predicate_ann(Token::Colon)                     })?                 } else {@@ -2786,49 +2790,45 @@ impl<I: Tokens> Parser<I> {                  expect!(p, Token::Arrow); -                let params: Vec<Pat> =-                    p.parse_paren_items_as_params(items_ref.clone(), trailing_comma)?;-+                // Conversion can fail and consumes its AST. Keep the original+                // cover expressions available for the conditional fallback.+                let params = p.parse_paren_items_as_params(paren_items.clone(), trailing_comma)?;                 let body: Box<ArrowFunctionBody> = p.parse_fn_block_or_expr_body(-                    async_span.is_some(),-                    false,-                    true,+                    FunctionKind::Arrow {+                        is_async: async_span.is_some(),+                    },                     params.is_simple_parameter_list(),                 )?; -                if will_expect_colon_for_cond && !p.input().is(Token::Colon) {+                if !p.input().is(Token::Colon) {                     trace_cur!(p, parse_arrow_in_cond__fail);                     unexpected!(p, "fail")                 } -                Ok(Some::<Box<Expr>>(-                    ArrowExpr {-                        span: p.span(expr_start),-                        is_async: async_span.is_some(),-                        is_generator: false,-                        params,-                        body,-                        return_type: Some(return_type),-                        ..Default::default()-                    }-                    .into(),-                ))+                Ok(Some((params, return_type, body)))             }) {-                if let Expr::Arrow(ArrowExpr { params, .. }) = expr.as_ref() {-                    self.record_await_in_arrow_params(params);-                }+                self.record_await_in_arrow_params(&params);                 self.emit_pending_async_arrow_param_await(pending_await);-                return Ok(expr);+                return Ok(ArrowExpr {+                    span: self.span(expr_start),+                    is_async: async_span.is_some(),+                    is_generator: false,+                    params,+                    body,+                    return_type: Some(return_type),+                    ..Default::default()+                }+                .into());             }         } -        let return_type = if !self.ctx().contains(Context::WillExpectColonForCond)+        let return_type = if !will_expect_colon_for_cond             && self.input().syntax().typescript()             && self.input().is(Token::Colon)         {             self.try_parse_ts(|p| {                 let return_type = if p.input().syntax().flow() {-                    p.do_inside_of_context(Context::DisallowFlowAnonFnType, |p| {+                    p.do_inside_of_type_context(TypeContext::DisallowFlowAnonFnType, |p| {                         p.parse_ts_type_or_type_predicate_ann(Token::Colon)                     })?                 } else {@@ -2861,7 +2861,7 @@ impl<I: Tokens> Parser<I> {                     }                 } -                if p.ctx().contains(Context::InGenerator)+                if p.includes_yield_expr()                     && matches!(                         param,                         Pat::Assign(AssignPat { right, .. })@@ -2894,9 +2894,9 @@ impl<I: Tokens> Parser<I> {             self.emit_pending_async_arrow_param_await(pending_await);              let body: Box<ArrowFunctionBody> = self.parse_fn_block_or_expr_body(-                async_span.is_some(),-                false,-                true,+                FunctionKind::Arrow {+                    is_async: async_span.is_some(),+                },                 params.is_simple_parameter_list(),             )?;             let arrow_expr = ArrowExpr {@@ -3074,28 +3074,15 @@ impl<I: Tokens> Parser<I> {         let try_parse_arrow_expr = |p: &mut Self, id: Ident, id_is_async| -> PResult<Box<Expr>> {             let cur = p.input().cur(); -            if id_is_async && cur.is_word() && !cur.is_reserved(p.ctx()) {+            if id_is_async && cur.is_word() && !p.token_is_reserved(cur) {                 if !p.input().had_line_break_before_cur() {-                    // see https://github.com/tc39/ecma262/issues/2034-                    // ```js-                    // for(async of-                    // for(async of x);-                    // for(async of =>{};;);-                    // ```-                    let ctx = p.ctx();-                    if ctx.contains(Context::ForLoopInit)-                        && p.input().is(Token::Of)-                        && !peek!(p).is_some_and(|peek| peek == Token::Arrow)-                    {-                        // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement-                        // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]-                        // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]-                        // ```--                        if !ctx.contains(Context::ForAwaitLoopInit) {-                            p.emit_err(p.input().prev_span(), SyntaxError::TS1106);-                        }-+                    // An identifier after `async` starts an arrow only when+                    // followed by `=>`. Keep TS `async as T` on its type-assertion+                    // path; leave other tokens to the enclosing production.+                    let is_type_assertion = p.syntax().typescript() && cur == Token::As;+                    let is_arrow = peek!(p).is_some_and(|peek| peek == Token::Arrow)+                        && !p.input_mut().has_linebreak_between_cur_and_peeked();+                    if !is_type_assertion && !is_arrow {                         return Ok(id.into());                     } @@ -3121,9 +3108,7 @@ impl<I: Tokens> Parser<I> {                     expect!(p, Token::Arrow);                     p.record_await_in_arrow_params(&params);                     let body = p.parse_fn_block_or_expr_body(-                        true,-                        false,-                        true,+                        FunctionKind::Arrow { is_async: true },                         params.is_simple_parameter_list(),                     )?; @@ -3145,9 +3130,7 @@ impl<I: Tokens> Parser<I> {                 p.bump();                 p.record_await_in_arrow_params(&params);                 let body = p.parse_fn_block_or_expr_body(-                    false,-                    false,-                    true,+                    FunctionKind::Arrow { is_async: false },                     params.is_simple_parameter_list(),                 )?; @@ -3166,11 +3149,7 @@ impl<I: Tokens> Parser<I> {         };          if cur == Token::Let || cur == Token::Await {-            let ctx = self.ctx();-            let id = self.parse_ident(-                !ctx.contains(Context::InGenerator),-                !ctx.contains(Context::InAsync),-            )?;+            let id = self.parse_ident(!self.includes_yield_expr(), !self.includes_await_expr())?;              if !can_be_arrow {                 return Ok(id.into());@@ -3189,7 +3168,9 @@ impl<I: Tokens> Parser<I> {             .into())         } else if cur == Token::Ident {             let word = self.input_mut().expect_word_token_and_bump();-            if self.ctx().contains(Context::InClassField) && word == atom!("arguments") {+            if self.boundary_ctx().contains(BoundaryContext::InClassField)+                && word == atom!("arguments")+            {                 self.emit_err(self.input().prev_span(), SyntaxError::ArgumentsInClassField)             };             let id = Ident::new_no_ctxt(word, self.span(start));

deps/swc/crates/swc_ecma_parser/src/parser/ident.rs

@@ -3,7 +3,13 @@ use swc_atoms::atom; use swc_common::BytePos; use swc_ecma_ast::*; -use crate::{error::SyntaxError, input::Tokens, lexer::Token, Context, PResult, Parser};+use crate::{+    error::SyntaxError,+    input::Tokens,+    lexer::Token,+    parser::{BoundaryContext, TypeContext},+    Context, PResult, Parser,+};  impl<I: Tokens> Parser<I> {     // https://tc39.es/ecma262/#prod-ModuleExportName@@ -26,8 +32,14 @@ impl<I: Tokens> Parser<I> {         let start = token_and_span.span.lo;         let cur = token_and_span.token;         let w = if cur.is_word() {-            self.input_mut().expect_word_token_and_bump()-        } else if cur == Token::JSXName && self.ctx().contains(Context::InType) {+            let word = if cur == Token::Ident {+                self.input_mut().expect_word_token_value()+            } else {+                cur.take_word(self.input())+            };+            self.input_mut().bump_without_escape_check();+            word+        } else if cur == Token::JSXName && self.type_ctx().contains(TypeContext::InType) {             self.input_mut().expect_jsx_name_token_and_bump()         } else {             syntax_error!(self, SyntaxError::ExpectedIdent)@@ -35,6 +47,29 @@ impl<I: Tokens> Parser<I> {         Ok(IdentName::new(w, self.span(start)))     } +    /// Retains keyword escape validation where TypeScript uses an identifier+    /// rather than an unrestricted property or qualified name.+    #[cfg(feature = "typescript")]+    pub(super) fn parse_ident_name_with_escape_check(&mut self) -> PResult<IdentName> {+        if let Some(error) = self.input().escaped_keyword_error() {+            // Escape errors must survive successful speculation. Parser checkpoints+            // discard them if the binding interpretation is rolled back.+            self.input_mut().iter_mut().add_error(error);+        }+        self.parse_ident_name()+    }++    /// Declaration names also obey parser-owned contextual keyword+    /// restrictions. Type-only names such as `infer` variables retain+    /// lexical-only checking.+    #[cfg(feature = "typescript")]+    pub(super) fn parse_ts_binding_ident_name(&mut self) -> PResult<IdentName> {+        if let Some(error) = self.escaped_keyword_error() {+            self.input_mut().iter_mut().add_error(error);+        }+        self.parse_ident_name()+    }+     pub(crate) fn parse_maybe_private_name(&mut self) -> PResult<Either<PrivateName, IdentName>> {         let is_private = self.input().is(Token::Hash);         if is_private {@@ -65,11 +100,7 @@ impl<I: Tokens> Parser<I> {     /// IdentifierReference     #[inline]     fn parse_ident_ref(&mut self) -> PResult<Ident> {-        let ctx = self.ctx();-        self.parse_ident(-            !ctx.contains(Context::InGenerator),-            !ctx.contains(Context::InAsync),-        )+        self.parse_ident(!self.includes_yield_expr(), !self.includes_await_expr())     }      /// LabelIdentifier@@ -91,7 +122,15 @@ impl<I: Tokens> Parser<I> {             let span = self.input().cur_span();             let word = self.input_mut().expect_word_token_and_bump();             if atom!("arguments") == word || atom!("eval") == word {-                self.emit_strict_mode_err(span, SyntaxError::EvalAndArgumentsInStrict);+                // Ambient function parameters do not create runtime bindings.+                // Keep strict-mode checks for variables and non-ambient types.+                let ambient_parameter = self.syntax().typescript()+                    && !self.syntax().flow()+                    && self.type_ctx().contains(TypeContext::InDeclare)+                    && self.boundary_ctx().contains(BoundaryContext::InParameters);+                if !ambient_parameter {+                    self.emit_strict_mode_err(span, SyntaxError::EvalAndArgumentsInStrict);+                }             }             return Ok(Ident::new_no_ctxt(word, span).into());         }@@ -99,9 +138,10 @@ impl<I: Tokens> Parser<I> {         // "yield" and "await" is **lexically** accepted.         let token = self.input().cur();         let ident = self.parse_ident(true, true)?;-        let ctx = self.ctx();-        if (ctx.intersects(Context::InAsync.union(Context::InStaticBlock)) && token == Token::Await)-            || (ctx.contains(Context::InGenerator) && token == Token::Yield)+        if ((self.includes_await_expr()+            || self.boundary_ctx().contains(BoundaryContext::InStaticBlock))+            && token == Token::Await)+            || (self.includes_yield_expr() && token == Token::Yield)         {             self.emit_err(ident.span, SyntaxError::ExpectedIdent);         }@@ -121,10 +161,17 @@ impl<I: Tokens> Parser<I> {             Ok(Some(                 Ident::new_no_ctxt(atom!("this"), self.span(start)).into(),             ))-        } else if cur.is_word() && !cur.is_reserved(self.ctx()) {-            self.parse_binding_ident(disallow_let).map(Some)         } else {-            Ok(None)+            // Strict mode alone does not reserve `await` in a function name.+            let is_await_ident = cur == Token::Await+                && !self.includes_await_expr()+                && !self.boundary_ctx().contains(BoundaryContext::InStaticBlock)+                && !self.ctx().contains(Context::Module);+            if is_await_ident || (cur.is_word() && !self.token_is_reserved(cur)) {+                self.parse_binding_ident(disallow_let).map(Some)+            } else {+                Ok(None)+            }         }     } @@ -134,6 +181,8 @@ impl<I: Tokens> Parser<I> {     pub(crate) fn parse_ident(&mut self, incl_yield: bool, incl_await: bool) -> PResult<Ident> {         trace_cur!(self, parse_ident); +        self.check_current_token_escape()?;+         let token_and_span = self.input().get_cur();         if !token_and_span.token.is_word() {             syntax_error!(self, SyntaxError::ExpectedIdent)@@ -172,16 +221,18 @@ impl<I: Tokens> Parser<I> {         // value as the StringValue of any ReservedWord except for yield or await.         if t == Token::Await {             let ctx = self.ctx();-            if ctx.contains(Context::InDeclare) {+            if self.type_ctx().contains(TypeContext::InDeclare) {                 word = atom!("await");-            } else if ctx.contains(Context::InStaticBlock) {+            } else if self.boundary_ctx().contains(BoundaryContext::InStaticBlock) {                 syntax_error!(self, span, SyntaxError::ExpectedIdent)-            } else if ctx.contains(Context::InAsync)+            } else if self.includes_await_expr()                 || (ctx.contains(Context::Module) && !self.syntax().flow())             {                 syntax_error!(self, span, SyntaxError::InvalidIdentInAsync)             } else if incl_await {-                if ctx.contains(Context::CanBeModule) && !self.syntax().flow() {+                if self.boundary_ctx().contains(BoundaryContext::CanBeModule)+                    && !self.syntax().flow()+                {                     self.emit_module_mode_err(span, SyntaxError::InvalidIdentInAsync);                 }                 self.record_await_in_async_arrow_params(span);@@ -198,7 +249,9 @@ impl<I: Tokens> Parser<I> {             word = ident         } else if t == Token::Ident {             let word = self.input_mut().expect_word_token_and_bump();-            if self.ctx().contains(Context::InClassField) && word == atom!("arguments") {+            if self.boundary_ctx().contains(BoundaryContext::InClassField)+                && word == atom!("arguments")+            {                 self.emit_err(span, SyntaxError::ArgumentsInClassField)             }             return Ok(Ident::new_no_ctxt(word, self.span(start)));

deps/swc/crates/swc_ecma_parser/src/parser/input.rs

@@ -3,7 +3,7 @@ use swc_common::{BytePos, Span}; use swc_ecma_ast::EsVersion;  use crate::{-    error::Error,+    error::{Error, SyntaxError},     lexer::{LexResult, NextTokenAndSpan, Token, TokenAndSpan, TokenFlags, TokenValue},     syntax::SyntaxFlags,     Context,@@ -42,9 +42,9 @@ pub trait Tokens: Clone {     ///     /// This errors should be dropped if it's not a module.     ///-    /// Implementor should check for if [Context].module, and buffer errors if-    /// module is false. Also, implementors should move errors to the error-    /// buffer on set_ctx if the parser mode become module mode.+    /// Implementors should buffer the error unless [`Context::Module`] is set.+    /// When `set_ctx` enables module mode, buffered module errors should be+    /// moved to the main error buffer.     fn add_module_mode_error(&mut self, error: Error);      fn end_pos(&self) -> BytePos;@@ -59,12 +59,13 @@ pub trait Tokens: Clone {         false     } -    /// Saves the diagnostic buffer lengths for a whole-program retry.+    /// Saves the diagnostic buffer lengths for speculative parsing or a+    /// whole-program retry.     fn diagnostic_checkpoint_save(&self) -> (usize, usize) {         (0, 0)     } -    /// Discards diagnostics emitted after a whole-program checkpoint.+    /// Discards diagnostics emitted after a parser checkpoint.     fn diagnostic_checkpoint_load(&mut self, _checkpoint: (usize, usize)) {}      /// If the program was parsed as a script, this contains the module@@ -99,6 +100,9 @@ pub trait Tokens: Clone {     fn scan_jsx_open_el_terminal_token(&mut self) -> TokenAndSpan;     fn rescan_jsx_open_el_terminal_token(&mut self, reset: BytePos) -> TokenAndSpan;     fn rescan_jsx_token(&mut self, reset: BytePos) -> TokenAndSpan;+    /// Rewinds a compound greater-than operator to its first `>`, discarding+    /// any diagnostics and buffered comments produced by later lookahead.+    fn rescan_type_gt(&mut self, span: Span) -> TokenAndSpan;     fn scan_jsx_identifier(&mut self, start: BytePos) -> TokenAndSpan;     fn scan_jsx_attribute_value(&mut self) -> TokenAndSpan;     fn rescan_template_token(&mut self, start: BytePos, start_with_back_tick: bool)@@ -333,7 +337,48 @@ impl<I: Tokens> Buffer<I> {         self.set_cur(first_token);     } +    #[inline(always)]+    pub(crate) fn has_escaped_keyword(&self) -> bool {+        // Keyword tokens only carry a word value when their spelling has escapes.+        (self.cur().is_keyword() || self.cur().is_known_ident())+            && matches!(self.get_token_value(), Some(TokenValue::Word(_)))+    }++    #[inline(always)]+    pub(crate) fn escaped_keyword_error(&self) -> Option<Error> {+        if !self.has_escaped_keyword() {+            return None;+        }+        let Some(TokenValue::Word(word)) = self.get_token_value() else {+            return None;+        };+        if !self.cur().is_reserved(self.iter.ctx()) {+            return None;+        }+        Some(Error::new(+            self.cur.span,+            SyntaxError::EscapeInReservedWord { word: word.clone() },+        ))+    }++    #[inline]     pub fn bump(&mut self) {+        if self.has_escaped_keyword() {+            self.report_escaped_keyword();+        }+        self.bump_without_escape_check();+    }++    #[cold]+    #[inline(never)]+    fn report_escaped_keyword(&mut self) {+        if let Some(error) = self.escaped_keyword_error() {+            self.iter.add_error(error);+        }+    }++    /// IdentifierName permits escaped reserved words, unlike keyword syntax.+    pub(crate) fn bump_without_escape_check(&mut self) {         let next = if self.next.is_none() {             self.iter.next_token()         } else {@@ -411,65 +456,20 @@ impl<I: Tokens> Buffer<I> {         self.set_cur(token);     } -    pub fn merge_lt_gt(&mut self) {-        debug_assert!(-            self.is(Token::Lt) || self.is(Token::Gt),-            "parser should only call merge_lt_gt when encountering Less token"-        );-        if self.peek().is_none() {-            return;+    /// Consumes one type-closing `>`. Rescanning is necessary because the+    /// suffix may combine with following source, as in `>=>` becoming `=>`.+    pub fn eat_type_gt(&mut self) -> bool {+        if self.cur().should_rescan_into_gt_in_jsx() {+            let had_line_break = self.cur.had_line_break;+            let mut token = self.iter.rescan_type_gt(self.cur.span);+            token.had_line_break = had_line_break;+            self.next = None;+            self.set_cur(token);+        } else if self.cur() != Token::Gt {+            return false;         }-        let span = self.cur_span();-        let next = self.next().unwrap();-        if span.hi != next.span().lo {-            return;-        }-        let next = self.next_mut().take().unwrap();-        let cur = self.get_cur();-        let cur_token = cur.token;-        let token = if cur_token == Token::Gt {-            let next_token = next.token();-            if next_token == Token::Gt {-                // >>-                Token::RShift-            } else if next_token == Token::Eq {-                // >=-                Token::GtEq-            } else if next_token == Token::RShift {-                // >>>-                Token::ZeroFillRShift-            } else if next_token == Token::GtEq {-                // >>=-                Token::RShiftEq-            } else if next_token == Token::RShiftEq {-                // >>>=-                Token::ZeroFillRShiftEq-            } else {-                self.set_next(Some(next));-                return;-            }-        } else if cur_token == Token::Lt {-            let next_token = next.token();-            if next_token == Token::Lt {-                // <<-                Token::LShift-            } else if next_token == Token::Eq {-                // <=-                Token::LtEq-            } else if next_token == Token::LtEq {-                // <<=-                Token::LShiftEq-            } else {-                self.set_next(Some(next));-                return;-            }-        } else {-            self.set_next(Some(next));-            return;-        };-        let span = span.with_hi(next.span().hi);-        let token = TokenAndSpan::new(token, span, cur.had_line_break);-        self.set_cur(token);+        self.bump();+        true     }      #[inline(always)]

deps/swc/crates/swc_ecma_parser/src/parser/jsx.rs

@@ -2,11 +2,11 @@ use swc_atoms::Atom; use swc_common::{BytePos, Span, Spanned}; use swc_ecma_ast::*; -use super::{input::Tokens, Parser};+use super::{input::Tokens, Parser, TypeContext}; use crate::{     error::SyntaxError,     lexer::{Token, TokenFlags},-    Context, PResult,+    PResult, };  pub(crate) enum ParsedJSXElement {@@ -218,36 +218,33 @@ impl<I: Tokens> Parser<I> {         match t {             Token::LessSlash => Ok(None),             Token::LBrace => Ok(Some({-                self.do_outside_of_context(-                    Context::InCondExpr.union(Context::WillExpectColonForCond),-                    |p| {-                        let start = p.cur_pos();-                        p.bump(); // bump "{"-                        let ret = if p.input().cur() == Token::DotDotDot {-                            p.bump(); // bump "..."-                            let expr = p.parse_expr()?;-                            p.expect_without_advance(Token::RBrace)?;-                            p.input_mut().scan_jsx_token();-                            JSXElementChild::JSXSpreadChild(JSXSpreadChild {-                                span: p.span(start),-                                expr,-                            })+                self.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                    let start = p.cur_pos();+                    p.bump(); // bump "{"+                    let ret = if p.input().cur() == Token::DotDotDot {+                        p.bump(); // bump "..."+                        let expr = p.parse_expr()?;+                        p.expect_without_advance(Token::RBrace)?;+                        p.input_mut().scan_jsx_token();+                        JSXElementChild::JSXSpreadChild(JSXSpreadChild {+                            span: p.span(start),+                            expr,+                        })+                    } else {+                        let expr = if p.input().cur() == Token::RBrace {+                            JSXExpr::JSXEmptyExpr(p.parse_jsx_empty_expr())                         } else {-                            let expr = if p.input().cur() == Token::RBrace {-                                JSXExpr::JSXEmptyExpr(p.parse_jsx_empty_expr())-                            } else {-                                p.parse_expr().map(JSXExpr::Expr)?-                            };-                            p.expect_without_advance(Token::RBrace)?;-                            p.input_mut().scan_jsx_token();-                            JSXElementChild::JSXExprContainer(JSXExprContainer {-                                span: p.span(start),-                                expr,-                            })+                            p.parse_expr().map(JSXExpr::Expr)?                         };-                        Ok(ret)-                    },-                )?+                        p.expect_without_advance(Token::RBrace)?;+                        p.input_mut().scan_jsx_token();+                        JSXElementChild::JSXExprContainer(JSXExprContainer {+                            span: p.span(start),+                            expr,+                        })+                    };+                    Ok(ret)+                })?             })),             Token::Lt => {                 let ele = self.parse_jsx_element(false)?;@@ -368,10 +365,10 @@ impl<I: Tokens> Parser<I> {         } else {             let start = self.input().cur_pos();             let name = self.parse_jsx_attr_name()?;-            let value = self.do_outside_of_context(-                Context::InCondExpr.union(Context::WillExpectColonForCond),-                |p| p.parse_jsx_attr_value(),-            )?;+            let value = self+                .do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+                    p.parse_jsx_attr_value()+                })?;             Ok(JSXAttrOrSpread::JSXAttr(JSXAttr {                 span: self.span(start),                 name,@@ -403,120 +400,111 @@ impl<I: Tokens> Parser<I> {          let start = self.cur_pos(); -        self.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {-            p.expect(Token::Lt)?;+        self.expect(Token::Lt)?;++        // Handle JSX fragment opening followed by '=': '<>='+        // When lexer sees '>=' it combines into GtEq, but JSX fragment only needs '>'+        // Use rescan_jsx_open_el_terminal_token to split >= back into >+        self.input_mut().rescan_jsx_open_el_terminal_token(); -            // Handle JSX fragment opening followed by '=': '<>='-            // When lexer sees '>=' it combines into GtEq, but JSX fragment only needs '>'-            // Use rescan_jsx_open_el_terminal_token to split >= back into >-            p.input_mut().rescan_jsx_open_el_terminal_token();+        #[cfg(feature = "tsrx")]+        if self.input().syntax().tsrx() && self.input().is(Token::LBrace) {+            return self+                .parse_tsrx_dynamic_jsx_element(start, in_expr_context)+                .map(ParsedJSXElement::Tsrx);+        } -            #[cfg(feature = "tsrx")]-            if p.input().syntax().tsrx() && p.input().is(Token::LBrace) {-                return p-                    .parse_tsrx_dynamic_jsx_element(start, in_expr_context)-                    .map(ParsedJSXElement::Tsrx);-            }+        if self.input().cur() == Token::Gt {+            // <>xxxxxx</>+            self.input_mut().scan_jsx_token();+            let opening = JSXOpeningFragment {+                span: self.span(start),+            };+            let children = self.parse_jsx_children();+            let closing = self.parse_jsx_closing_fragment(in_expr_context)?;+            let span = self.span(start);+            Ok(ParsedJSXElement::Fragment(JSXFragment {+                span,+                opening,+                children,+                closing,+            }))+        } else {+            let name = self.parse_jsx_element_name()?;+            let type_args = if self.input().syntax().typescript() && self.input().is(Token::Lt) {+                self.try_parse_ts(|parser| parser.parse_ts_type_args().map(Some))+            } else {+                None+            };+            let attrs = self.parse_jsx_attrs()?;+            if self.input().cur() == Token::Gt {+                let opening_span = Span::new_with_checked(start, self.input().cur_span().hi);+                let opening = JSXOpeningElement {+                    span: opening_span,+                    name,+                    type_args,+                    attrs,+                    self_closing: false,+                };++                #[cfg(feature = "tsrx")]+                if self.input().syntax().tsrx()+                    && matches!(&opening.name, JSXElementName::Ident(name) if name.sym == "style")+                {+                    return self+                        .parse_tsrx_raw_style_element(start, opening, in_expr_context)+                        .map(ParsedJSXElement::Element);+                } -            if p.input().cur() == Token::Gt {-                // <>xxxxxx</>-                p.input_mut().scan_jsx_token();-                let opening = JSXOpeningFragment {-                    span: p.span(start),+                // <xxxxx>xxxxx</xxxxx>+                self.input_mut().scan_jsx_token();+                let children = self.parse_jsx_children();+                let closing = self.parse_jsx_closing_element(in_expr_context, &opening.name)?;+                let span = if in_expr_context {+                    Span::new_with_checked(start, self.last_pos())+                } else {+                    Span::new_with_checked(start, self.cur_pos())                 };-                let children = p.parse_jsx_children();-                let closing = p.parse_jsx_closing_fragment(in_expr_context)?;-                let span = p.span(start);-                Ok(ParsedJSXElement::Fragment(JSXFragment {+                Ok(ParsedJSXElement::Element(JSXElement {                     span,                     opening,                     children,-                    closing,+                    closing: Some(closing),                 }))             } else {-                let name = p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {-                    p.parse_jsx_element_name()-                })?;-                let type_args = if p.input().syntax().typescript() && p.input().is(Token::Lt) {-                    p.try_parse_ts(|p| {-                        let ret = p.parse_ts_type_args()?;-                        p.assert_and_bump(Token::Gt);-                        Ok(Some(ret))-                    })+                // <xxxxx/>+                self.expect(Token::Slash)?;++                // Handle JSX self-closing tag followed by '=': '<tag/>='+                // When lexer sees '>=' it combines into GtEq, but JSX only needs '>'+                // Use rescan_jsx_open_el_terminal_token to split >= back into >+                self.input_mut().rescan_jsx_open_el_terminal_token();+                self.expect_without_advance(Token::Gt)?;++                if in_expr_context {+                    self.bump();                 } else {-                    None+                    self.input_mut().scan_jsx_token();+                }+                let span = if in_expr_context {+                    self.span(start)+                } else {+                    Span::new_with_checked(start, self.cur_pos())                 };-                let attrs = p.parse_jsx_attrs()?;-                if p.input().cur() == Token::Gt {-                    let opening_span =-                        Span::new_with_checked(start, p.input().cur_span().hi);-                    let opening = JSXOpeningElement {-                        span: opening_span,+                Ok(ParsedJSXElement::Element(JSXElement {+                    span,+                    opening: JSXOpeningElement {+                        span,                         name,                         type_args,                         attrs,-                        self_closing: false,-                    };--                    #[cfg(feature = "tsrx")]-                    if p.input().syntax().tsrx()-                        && matches!(&opening.name, JSXElementName::Ident(name) if name.sym == "style")-                    {-                        return p-                            .parse_tsrx_raw_style_element(start, opening, in_expr_context)-                            .map(ParsedJSXElement::Element);-                    }--                    // <xxxxx>xxxxx</xxxxx>-                    p.input_mut().scan_jsx_token();-                    let children = p.parse_jsx_children();-                    let closing = p.parse_jsx_closing_element(in_expr_context, &opening.name)?;-                    let span = if in_expr_context {-                        Span::new_with_checked(start, p.last_pos())-                    } else {-                        Span::new_with_checked(start, p.cur_pos())-                    };-                    Ok(ParsedJSXElement::Element(JSXElement {-                        span,-                        opening,-                        children,-                        closing: Some(closing),-                    }))-                } else {-                    // <xxxxx/>-                    p.expect(Token::Slash)?;--                    // Handle JSX self-closing tag followed by '=': '<tag/>='-                    // When lexer sees '>=' it combines into GtEq, but JSX only needs '>'-                    // Use rescan_jsx_open_el_terminal_token to split >= back into >-                    p.input_mut().rescan_jsx_open_el_terminal_token();-                    p.expect_without_advance(Token::Gt)?;--                    if in_expr_context {-                        p.bump();-                    } else {-                        p.input_mut().scan_jsx_token();-                    }-                    let span = if in_expr_context {-                        p.span(start)-                    } else {-                        Span::new_with_checked(start, p.cur_pos())-                    };-                    Ok(ParsedJSXElement::Element(JSXElement {-                        span,-                        opening: JSXOpeningElement {-                            span,-                            name,-                            type_args,-                            attrs,-                            self_closing: true,-                        },-                        children: Vec::new(),-                        closing: None,-                    }))-                }+                        self_closing: true,+                    },+                    children: Vec::new(),+                    closing: None,+                }))             }-        })+        }     } } 

deps/swc/crates/swc_ecma_parser/src/parser/macros.rs

@@ -1,3 +1,41 @@+/// Derives ECMAScript grammatical parameters and returns their previous values.+/// `+` enables a parameter, `~` disables it, and `?` explicitly inherits it.+/// Unlisted parameters are unchanged. Restoration belongs to the caller.+/// https://tc39.es/ecma262/#sec-grammatical-parameters+macro_rules! enter_grammar_context {+    (@flag $parameter:ident) => {+        $crate::parser::context::GrammarContext::$parameter+    };+    (@set $context:ident, + $parameter:ident) => {+        $context.insert(enter_grammar_context!(@flag $parameter));+    };+    (@set $context:ident, ~ $parameter:ident) => {+        $context.remove(enter_grammar_context!(@flag $parameter));+    };+    (@set $context:ident, ? $parameter:ident) => {+        // Validate inherited parameter names without changing their values.+        let _ = enter_grammar_context!(@flag $parameter);+    };+    ($context:expr, $($modifier:tt $parameter:ident),+ $(,)?) => {{+        let context = $context;+        let previous = *context;+        $(enter_grammar_context!(@set context, $modifier $parameter);)++        previous+    }};+}++/// Parses within explicit grammatical arguments, restoring them on success or+/// error. Only GrammarContext is scoped; lexical and other parser contexts are+/// unaffected.+macro_rules! with_grammar_context {+    ($parser:expr, [$($modifier:tt $parameter:ident),+ $(,)?], $parse:expr $(,)?) => {{+        let parser = &mut *$parser;+        let mut context = parser.grammar_ctx();+        enter_grammar_context!(&mut context, $($modifier $parameter),+);+        parser.with_grammar_context(context, $parse)+    }};+}+ macro_rules! trace_cur {     ($p:expr, $name:ident) => {{         if cfg!(feature = "debug") {

deps/swc/crates/swc_ecma_parser/src/parser/mod.rs

@@ -9,7 +9,7 @@ use swc_ecma_ast::*; use crate::{     error::SyntaxError,     input::Buffer,-    lexer::{Token, TokenAndSpan},+    lexer::{Token, TokenAndSpan, TokenValue},     parser::{         input::Tokens,         state::{State, WithState},@@ -28,6 +28,7 @@ use crate::error::Error; #[macro_use] mod macros; mod class_and_fn;+mod context; mod expr; mod ident; pub mod input;@@ -49,14 +50,25 @@ mod util; #[cfg(feature = "verify")] mod verifier; +use self::context::{+    BoundaryContext, FunctionKind, GrammarContext, StatementContext, StatementGrammar,+    SyntaxContext, TypeContext,+};+ pub type PResult<T> = Result<T, crate::error::Error>;  #[cfg(feature = "typescript")] pub struct ParserCheckpoint<I: Tokens> {     lexer: I::Checkpoint,+    diagnostic_lengths: (usize, usize),     buffer_prev_span: Span,     buffer_cur: TokenAndSpan,     buffer_next: Option<crate::lexer::NextTokenAndSpan>,+    grammar_context: GrammarContext,+    boundary_context: BoundaryContext,+    syntax_context: SyntaxContext,+    statement_context: StatementContext,+    type_context: TypeContext,     #[cfg(feature = "flow")]     allow_super_call: bool, }@@ -72,6 +84,11 @@ struct ProgramCheckpoint<I: Tokens> {     program_parse_mode: ProgramParseMode,     diagnostic_lengths: (usize, usize),     token_flags: crate::lexer::TokenFlags,+    grammar_context: GrammarContext,+    boundary_context: BoundaryContext,+    syntax_context: SyntaxContext,+    statement_context: StatementContext,+    type_context: TypeContext,     #[cfg(feature = "flow")]     allow_super_call: bool, }@@ -107,6 +124,17 @@ enum ProgramGrammar { pub struct Parser<I: self::input::Tokens> {     state: State,     input: self::input::Buffer<I>,+    /// ECMAScript `[In]`, `[Yield]`, `[Await]`, and `[Return]` grammatical+    /// parameters.+    grammar_context: GrammarContext,+    /// State inherited across function, class, and program boundaries.+    boundary_context: BoundaryContext,+    /// The nearest syntactic region used for parser early errors.+    syntax_context: SyntaxContext,+    /// State derived from the enclosing statement grammar.+    statement_context: StatementContext,+    /// TypeScript, Flow, and cover-grammar parsing state.+    type_context: TypeContext,     found_module_item: bool,     #[cfg(feature = "tsrx")]     tsrx: tsrx::TsrxState,@@ -152,6 +180,11 @@ impl<I: Tokens> Parser<I> {             program_parse_mode: self.program_parse_mode,             diagnostic_lengths: self.input.iter.diagnostic_checkpoint_save(),             token_flags: self.input.iter.token_flags(),+            grammar_context: self.grammar_context,+            boundary_context: self.boundary_context,+            syntax_context: self.syntax_context,+            statement_context: self.statement_context,+            type_context: self.type_context,             #[cfg(feature = "flow")]             allow_super_call: self.allow_super_call,         }@@ -172,6 +205,11 @@ impl<I: Tokens> Parser<I> {         self.found_module_item = checkpoint.found_module_item;         self.ambiguous_script_different_ast = checkpoint.ambiguous_script_different_ast;         self.program_parse_mode = checkpoint.program_parse_mode;+        self.grammar_context = checkpoint.grammar_context;+        self.boundary_context = checkpoint.boundary_context;+        self.syntax_context = checkpoint.syntax_context;+        self.statement_context = checkpoint.statement_context;+        self.type_context = checkpoint.type_context;         #[cfg(feature = "flow")]         {             self.allow_super_call = checkpoint.allow_super_call;@@ -182,9 +220,15 @@ impl<I: Tokens> Parser<I> {     fn checkpoint_save(&self) -> ParserCheckpoint<I> {         ParserCheckpoint {             lexer: self.input.iter.checkpoint_save(),+            diagnostic_lengths: self.input.iter.diagnostic_checkpoint_save(),             buffer_cur: self.input.cur,             buffer_next: self.input.next.clone(),             buffer_prev_span: self.input.prev_span,+            grammar_context: self.grammar_context,+            boundary_context: self.boundary_context,+            syntax_context: self.syntax_context,+            statement_context: self.statement_context,+            type_context: self.type_context,             allow_super_call: self.allow_super_call,         }     }@@ -193,27 +237,49 @@ impl<I: Tokens> Parser<I> {     fn checkpoint_save(&self) -> ParserCheckpoint<I> {         ParserCheckpoint {             lexer: self.input.iter.checkpoint_save(),+            diagnostic_lengths: self.input.iter.diagnostic_checkpoint_save(),             buffer_cur: self.input.cur,             buffer_next: self.input.next.clone(),             buffer_prev_span: self.input.prev_span,+            grammar_context: self.grammar_context,+            boundary_context: self.boundary_context,+            syntax_context: self.syntax_context,+            statement_context: self.statement_context,+            type_context: self.type_context,         }     }      #[cfg(all(feature = "typescript", feature = "flow"))]     fn checkpoint_load(&mut self, checkpoint: ParserCheckpoint<I>) {         self.input.iter.checkpoint_load(checkpoint.lexer);+        self.input+            .iter+            .diagnostic_checkpoint_load(checkpoint.diagnostic_lengths);         self.input.cur = checkpoint.buffer_cur;         self.input.next = checkpoint.buffer_next;         self.input.prev_span = checkpoint.buffer_prev_span;+        self.grammar_context = checkpoint.grammar_context;+        self.boundary_context = checkpoint.boundary_context;+        self.syntax_context = checkpoint.syntax_context;+        self.statement_context = checkpoint.statement_context;+        self.type_context = checkpoint.type_context;         self.allow_super_call = checkpoint.allow_super_call;     }      #[cfg(all(feature = "typescript", not(feature = "flow")))]     fn checkpoint_load(&mut self, checkpoint: ParserCheckpoint<I>) {         self.input.iter.checkpoint_load(checkpoint.lexer);+        self.input+            .iter+            .diagnostic_checkpoint_load(checkpoint.diagnostic_lengths);         self.input.cur = checkpoint.buffer_cur;         self.input.next = checkpoint.buffer_next;         self.input.prev_span = checkpoint.buffer_prev_span;+        self.grammar_context = checkpoint.grammar_context;+        self.boundary_context = checkpoint.boundary_context;+        self.syntax_context = checkpoint.syntax_context;+        self.statement_context = checkpoint.statement_context;+        self.type_context = checkpoint.type_context;     }      #[cfg(feature = "flow")]@@ -254,6 +320,17 @@ impl<I: Tokens> Parser<I> {     fn mark_found_module_item(&mut self) {         self.found_module_item = true;     }++    /// Allows module-only syntax in a direct parser entry point without+    /// changing the source to module code.+    ///+    /// Program-level entry points configure this automatically. This is+    /// intended for callers which invoke methods such as [`Parser::parse_expr`]+    /// directly and need constructs such as `import.meta` or top-level `await`.+    #[inline(always)]+    pub fn allow_module_syntax(&mut self) {+        self.set_boundary_ctx(self.boundary_ctx() | BoundaryContext::CanBeModule);+    } }  impl<'a> Parser<crate::lexer::Lexer<'a>> {@@ -264,16 +341,23 @@ impl<'a> Parser<crate::lexer::Lexer<'a>> { }  impl<I: Tokens> Parser<I> {-    pub fn new_from(mut input: I) -> Self {+    pub fn new_from(input: I) -> Self {         let in_declare = input.syntax().dts();-        let mut ctx = input.ctx() | Context::TopLevel;-        ctx.set(Context::InDeclare, in_declare);-        input.set_ctx(ctx);+        let grammar_context = GrammarContext::empty();+        let boundary_context = BoundaryContext::TopLevel;+        let statement_context = StatementContext::empty();+        let mut type_context = TypeContext::empty();+        type_context.set(TypeContext::InDeclare, in_declare);          let start_pos = input.start_pos();         let mut p = Parser {             state: Default::default(),             input: crate::parser::input::Buffer::new(input),+            grammar_context,+            boundary_context,+            syntax_context: SyntaxContext::Program,+            statement_context,+            type_context,             found_module_item: false,             #[cfg(feature = "tsrx")]             tsrx: Default::default(),@@ -304,12 +388,15 @@ impl<I: Tokens> Parser<I> {      pub fn parse_script(&mut self) -> PResult<Script> {         trace_cur!(self, parse_script);+        self.enter_program_syntax_context();          #[cfg(feature = "tsrx")]         self.tsrx.enter_non_module(); -        let ctx = (self.ctx() & !Context::Module) | Context::TopLevel;-        self.set_ctx(ctx);+        let lexical_context = self.ctx() - Context::Module;+        self.input_mut().set_ctx(lexical_context);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, ~Return);+        self.set_boundary_ctx(self.boundary_ctx() | BoundaryContext::TopLevel);          let start = self.cur_pos(); @@ -329,15 +416,17 @@ impl<I: Tokens> Parser<I> {      pub fn parse_commonjs(&mut self) -> PResult<Script> {         trace_cur!(self, parse_commonjs);+        self.enter_program_syntax_context();          #[cfg(feature = "tsrx")]         self.tsrx.enter_non_module();          // CommonJS module is acctually in a function scope-        let ctx = (self.ctx() & !Context::Module)-            | Context::InFunction-            | Context::InsideNonArrowFunctionScope;-        self.set_ctx(ctx);+        let lexical_context = self.ctx() - Context::Module;+        self.input_mut().set_ctx(lexical_context);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, +Return);+        self.syntax_context = SyntaxContext::FunctionBody;+        self.set_boundary_ctx(self.boundary_ctx() | BoundaryContext::InsideNonArrowFunctionScope);          let start = self.cur_pos();         let shebang = self.parse_shebang()?;@@ -356,6 +445,7 @@ impl<I: Tokens> Parser<I> {      pub fn parse_typescript_module(&mut self) -> PResult<Module> {         trace_cur!(self, parse_typescript_module);+        self.enter_program_syntax_context();          #[cfg(feature = "tsrx")]         self.tsrx.enter_module();@@ -363,9 +453,11 @@ impl<I: Tokens> Parser<I> {         debug_assert!(self.syntax().typescript());          //TODO: parse() -> PResult<Program>-        let ctx = (self.ctx() | Context::Module | Context::TopLevel) & !Context::Strict;+        let ctx = (self.ctx() | Context::Module) - Context::Strict;         // Module code is always in strict mode-        self.set_ctx(ctx);+        self.input_mut().set_ctx(ctx);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, ~Return);+        self.set_boundary_ctx(self.boundary_ctx() | BoundaryContext::TopLevel);          let start = self.cur_pos();         let shebang = self.parse_shebang()?;@@ -483,13 +575,12 @@ impl<I: Tokens> Parser<I> {          let ret = match grammar {             ProgramGrammar::Module => {-                let ctx = self.ctx()-                    | Context::Module-                    | Context::CanBeModule-                    | Context::TopLevel-                    | Context::Strict;+                let ctx = self.ctx() | Context::Module | Context::Strict;                 // Emit buffered strict mode / module code violations.-                self.input.set_ctx(ctx);+                self.input_mut().set_ctx(ctx);+                self.set_boundary_ctx(+                    self.boundary_ctx() | BoundaryContext::CanBeModule | BoundaryContext::TopLevel,+                );                 if self.syntax().flow() {                     self.report_duplicate_exports(&body);                 }@@ -500,8 +591,13 @@ impl<I: Tokens> Parser<I> {                 })             }             ProgramGrammar::Script => {-                let ctx = self.ctx() & !Context::Module & !Context::CanBeModule & !Context::InAsync;-                self.input.set_ctx(ctx | Context::TopLevel);+                let lexical_context = self.ctx() - Context::Module;+                self.input_mut().set_ctx(lexical_context);+                self.set_boundary_ctx(+                    (self.boundary_ctx() - BoundaryContext::CanBeModule)+                        | BoundaryContext::TopLevel,+                );+                enter_grammar_context!(&mut self.grammar_context, ?Yield, ~Await, ?Return);                  let requires_module_ast = body                     .iter()@@ -541,15 +637,26 @@ impl<I: Tokens> Parser<I> {     }      fn enter_unambiguous_module_context(&mut self) {-        let ctx = (self.ctx() & !Context::Module) | Context::CanBeModule | Context::TopLevel;-        self.set_ctx(ctx);+        self.enter_program_syntax_context();+        let lexical_context = self.ctx() - Context::Module;+        self.input_mut().set_ctx(lexical_context);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, ~Return);+        self.set_boundary_ctx(+            self.boundary_ctx() | BoundaryContext::CanBeModule | BoundaryContext::TopLevel,+        );         self.program_parse_mode = ProgramParseMode::Module;         self.ambiguous_script_different_ast = false;     }      fn enter_unambiguous_script_context(&mut self) {-        let ctx = self.ctx() & !Context::Module & !Context::CanBeModule & !Context::InAsync;-        self.set_ctx(ctx | Context::TopLevel);+        self.enter_program_syntax_context();+        let lexical_context = self.ctx() - Context::Module;+        self.input_mut().set_ctx(lexical_context);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, ~Return);+        self.set_boundary_ctx(+            (self.boundary_ctx() - BoundaryContext::CanBeModule) | BoundaryContext::TopLevel,+        );+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ~Await, ?Return);         self.program_parse_mode = ProgramParseMode::Script;         self.ambiguous_script_different_ast = false;     }@@ -587,25 +694,26 @@ impl<I: Tokens> Parser<I> {      fn can_classify_module(&self) -> bool {         self.is_unambiguous_module()-            && !self.ctx().intersects(-                Context::InFunction-                    .union(Context::InParameters)-                    .union(Context::InClassField)-                    .union(Context::InStaticBlock),+            && self.syntax_context == SyntaxContext::Program+            && !self.boundary_ctx().intersects(+                BoundaryContext::InParameters+                    .union(BoundaryContext::InClassField)+                    .union(BoundaryContext::InStaticBlock),             )     }      pub fn parse_module(&mut self) -> PResult<Module> {+        self.enter_program_syntax_context();         #[cfg(feature = "tsrx")]         self.tsrx.enter_module(); -        let ctx = self.ctx()-            | Context::Module-            | Context::CanBeModule-            | Context::TopLevel-            | Context::Strict;+        let ctx = self.ctx() | Context::Module | Context::Strict;         // Module code is always in strict mode-        self.set_ctx(ctx);+        self.input_mut().set_ctx(ctx);+        enter_grammar_context!(&mut self.grammar_context, ?Yield, ?Await, ~Return);+        self.set_boundary_ctx(+            self.boundary_ctx() | BoundaryContext::CanBeModule | BoundaryContext::TopLevel,+        );          let start = self.cur_pos();         let shebang = self.parse_shebang()?;@@ -681,6 +789,187 @@ impl<I: Tokens> Parser<I> {         self.input_mut().set_ctx(ctx);     } +    #[inline(always)]+    fn grammar_ctx(&self) -> GrammarContext {+        self.grammar_context+    }++    #[inline(always)]+    fn set_grammar_ctx(&mut self, grammar_context: GrammarContext) {+        self.grammar_context = grammar_context;+    }++    #[inline(always)]+    fn boundary_ctx(&self) -> BoundaryContext {+        self.boundary_context+    }++    #[inline(always)]+    fn set_boundary_ctx(&mut self, boundary_context: BoundaryContext) {+        self.boundary_context = boundary_context;+    }++    #[inline(always)]+    fn statement_ctx(&self) -> StatementContext {+        self.statement_context+    }++    #[inline(always)]+    fn set_statement_ctx(&mut self, statement_context: StatementContext) {+        self.statement_context = statement_context;+    }++    #[inline(always)]+    fn type_ctx(&self) -> TypeContext {+        self.type_context+    }++    #[inline(always)]+    fn set_type_ctx(&mut self, type_context: TypeContext) {+        self.type_context = type_context;+    }++    /// Runs with derived grammatical parameters, restoring them on success or+    /// error.+    #[inline]+    fn with_grammar_context<T>(+        &mut self,+        context: GrammarContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let grammar_context = self.grammar_ctx();+        self.set_grammar_ctx(context);+        let result = f(self);+        self.set_grammar_ctx(grammar_context);+        result+    }++    #[inline]+    fn do_inside_of_boundary_context<T>(+        &mut self,+        context: BoundaryContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.boundary_ctx();+        self.set_boundary_ctx(previous | context);+        let result = f(self);+        self.set_boundary_ctx(previous);+        result+    }++    #[inline]+    fn do_outside_of_boundary_context<T>(+        &mut self,+        context: BoundaryContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.boundary_ctx();+        self.set_boundary_ctx(previous - context);+        let result = f(self);+        self.set_boundary_ctx(previous);+        result+    }++    #[inline]+    fn do_inside_of_statement_context<T>(+        &mut self,+        context: StatementContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.statement_ctx();+        self.set_statement_ctx(previous | context);+        let result = f(self);+        self.set_statement_ctx(previous);+        result+    }++    #[inline]+    fn do_outside_of_statement_context<T>(+        &mut self,+        context: StatementContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.statement_ctx();+        self.set_statement_ctx(previous - context);+        let result = f(self);+        self.set_statement_ctx(previous);+        result+    }++    #[inline]+    fn do_inside_of_type_context<T>(+        &mut self,+        context: TypeContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.type_ctx();+        self.set_type_ctx(previous | context);+        let result = f(self);+        self.set_type_ctx(previous);+        result+    }++    #[inline]+    fn do_outside_of_type_context<T>(+        &mut self,+        context: TypeContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.type_ctx();+        self.set_type_ctx(previous - context);+        let result = f(self);+        self.set_type_ctx(previous);+        result+    }++    #[inline(always)]+    fn includes_in_expr(&self) -> bool {+        self.grammar_ctx().contains(GrammarContext::In)+    }++    #[inline(always)]+    fn includes_yield_expr(&self) -> bool {+        self.grammar_ctx().contains(GrammarContext::Yield)+    }++    #[inline(always)]+    fn includes_await_expr(&self) -> bool {+        self.grammar_ctx().contains(GrammarContext::Await)+    }++    /// Applies the class initializer early error to every syntactic form of+    /// `await`, independently of the enclosing production's Await parameter.+    /// Function bodies establish their own boundary, even inside a class field.+    fn is_await_forbidden_in_initializer(&self) -> bool {+        self.syntax_context != SyntaxContext::FunctionBody+            && self+                .boundary_ctx()+                .intersects(BoundaryContext::InClassField | BoundaryContext::InStaticBlock)+    }++    #[inline(always)]+    fn allows_return_stmt(&self) -> bool {+        self.grammar_ctx().contains(GrammarContext::Return)+    }++    fn enter_program_syntax_context(&mut self) {+        self.syntax_context = SyntaxContext::Program;+    }++    /// Runs within a nested syntactic region, restoring it on success or error.+    /// These regions cannot classify their enclosing program as a module.+    fn with_syntax_context<T>(+        &mut self,+        context: SyntaxContext,+        f: impl FnOnce(&mut Self) -> T,+    ) -> T {+        let previous = self.syntax_context;+        self.syntax_context = context;+        let result = f(self);+        self.syntax_context = previous;+        result+    }+     #[inline]     pub fn do_inside_of_context<T>(         &mut self,@@ -714,25 +1003,100 @@ impl<I: Tokens> Parser<I> {         self.do_inside_of_context(Context::Strict, f)     } -    /// Original context is restored when returned guard is dropped.+    /// Enters type grammar without changing tokenization. Only the type-grammar+    /// flag is restored on exit; unrelated parser state is left intact.     #[inline(always)]     pub fn in_type<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {-        self.do_inside_of_context(Context::InType, f)+        let previous = self.type_context.contains(TypeContext::InType);+        self.type_context.insert(TypeContext::InType);+        let result = f(self);+        self.type_context.set(TypeContext::InType, previous);+        result     }      #[inline(always)]-    pub fn allow_in_expr<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {-        self.do_inside_of_context(Context::IncludeInExpr, f)+    pub fn syntax(&self) -> SyntaxFlags {+        self.input().syntax()     }      #[inline(always)]-    pub fn disallow_in_expr<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {-        self.do_outside_of_context(Context::IncludeInExpr, f)+    fn token_is_reserved(&self, token: Token) -> bool {+        match token {+            Token::Await => {+                self.includes_await_expr()+                    || self.boundary_ctx().contains(BoundaryContext::InStaticBlock)+                    || self.ctx().contains(Context::Module)+            }+            Token::Yield => self.includes_yield_expr() || self.ctx().contains(Context::Strict),+            _ => token.is_reserved(self.ctx()),+        }     }      #[inline(always)]-    pub fn syntax(&self) -> SyntaxFlags {-        self.input().syntax()+    fn word_is_reserved(&self, word: &Atom) -> bool {+        match &**word {+            "null" | "true" | "false" | "break" | "case" | "catch" | "continue" | "debugger"+            | "default" | "do" | "export" | "else" | "finally" | "for" | "function" | "if"+            | "return" | "switch" | "throw" | "try" | "var" | "const" | "while" | "with"+            | "new" | "this" | "super" | "class" | "extends" | "import" | "in" | "instanceof"+            | "typeof" | "void" | "delete" | "enum" => true,+            "let" => self.ctx().contains(Context::Strict),+            "await" => {+                self.includes_await_expr()+                    || self.boundary_ctx().contains(BoundaryContext::InStaticBlock)+                    || self.ctx().contains(Context::Module)+            }+            "yield" => self.includes_yield_expr() || self.ctx().contains(Context::Strict),+            "implements" | "package" | "protected" | "interface" | "private" | "public"+                if self.ctx().contains(Context::Strict) =>+            {+                true+            }+            _ => false,+        }+    }++    /// Captures an escape diagnostic before an IdentifierName's role is known.+    /// Contextual keywords depend on parser grammar, not just lexical state.+    /// Read the buffered token value: lexer flags may describe lookahead+    /// instead.+    #[inline]+    fn escaped_keyword_error(&self) -> Option<Error> {+        if !self.input().has_escaped_keyword() {+            return None;+        }+        self.reserved_word_escape_error()+    }++    /// Checks contextual reservation and owns the diagnostic only on the+    /// uncommon path containing an escaped keyword.+    #[cold]+    #[inline(never)]+    fn reserved_word_escape_error(&self) -> Option<Error> {+        if !self.token_is_reserved(self.input().cur()) {+            return None;+        }+        let Some(TokenValue::Word(word)) = self.input().get_token_value() else {+            return None;+        };+        Some(Error::new(+            self.input().cur_span(),+            SyntaxError::EscapeInReservedWord { word: word.clone() },+        ))+    }++    fn check_current_token_escape(&mut self) -> PResult<()> {+        let token = self.input().cur();+        // Buffer validates the other keywords when they are consumed. Only+        // these contextual keywords depend on parser-owned grammar parameters.+        if !matches!(token, Token::Await | Token::Yield) {+            return Ok(());+        }+        if let Some(error) = self.escaped_keyword_error() {+            return Err(error);+        }++        Ok(())     }      #[cold]@@ -1078,17 +1442,17 @@ impl<I: Tokens> Parser<I> {                 raw: None,             })         } else if cur.is_word() {-            let w = self.input_mut().expect_word_token_and_bump();-            PropName::Ident(IdentName::new(w, self.span(start)))+            PropName::Ident(self.parse_ident_name()?)         } else if cur == Token::LBracket {             self.bump();             let inner_start = self.input().cur_pos();-            let mut expr = self.allow_in_expr(Self::parse_assignment_expr)?;+            let mut expr =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;             if self.syntax().typescript() && self.input().is(Token::Comma) {                 let mut exprs = vec![expr];                 while self.input_mut().eat(Token::Comma) {                     //-                    exprs.push(self.allow_in_expr(Self::parse_assignment_expr)?);+                    exprs.push(with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?);                 }                 self.emit_err(self.span(inner_start), SyntaxError::TS1171);                 expr = Box::new(@@ -1116,13 +1480,12 @@ impl<I: Tokens> Parser<I> {     #[inline]     pub fn is_ident_ref(&mut self) -> bool {         let cur = self.input().cur();-        cur.is_word() && !cur.is_reserved(self.ctx())+        cur.is_word() && !self.token_is_reserved(cur)     }      #[inline]     pub fn peek_is_ident_ref(&mut self) -> bool {-        let ctx = self.ctx();-        peek!(self).is_some_and(|peek| peek.is_word() && !peek.is_reserved(ctx))+        peek!(self).is_some_and(|peek| peek.is_word() && !self.token_is_reserved(peek))     }      #[inline(always)]

deps/swc/crates/swc_ecma_parser/src/parser/module_item.rs

@@ -2,15 +2,23 @@ use swc_atoms::atom; use swc_common::Span; use swc_ecma_ast::*; -use crate::{error::SyntaxError, input::Tokens, lexer::Token, Context, PResult, Parser};+use crate::{+    error::SyntaxError,+    input::Tokens,+    lexer::Token,+    parser::{BoundaryContext, StatementGrammar, TypeContext},+    Context, PResult, Parser,+};  impl<I: Tokens> Parser<I> {     pub fn parse_module_item(&mut self) -> PResult<ModuleItem> {-        self.do_inside_of_context(Context::TopLevel, |p| {-            p.parse_stmt_like(true, handle_import_export)+        self.do_inside_of_boundary_context(BoundaryContext::TopLevel, |p| {+            p.parse_stmt_like(StatementGrammar::ModuleItem, handle_import_export)         })     } +    /// Leaves a closing delimiter buffered so the container owns its+    /// consumption.     pub(crate) fn parse_module_item_block_body(         &mut self,         allow_directives: bool,@@ -19,6 +27,26 @@ impl<I: Tokens> Parser<I> {         self.parse_block_body(allow_directives, end, handle_import_export)     } +    /// Parses attributes shared by imports and re-exports. Only the legacy+    /// `assert` keyword has a no-line-terminator restriction before it.+    fn parse_import_attributes(&mut self) -> PResult<Option<Box<ObjectLit>>> {+        if !self.input().syntax().import_attributes() {+            return Ok(None);+        }++        match self.input().cur() {+            Token::With => {}+            Token::Assert if !self.input().had_line_break_before_cur() => {}+            _ => return Ok(None),+        }+        self.bump();++        match self.parse_object_expr()? {+            Expr::Object(v) => Ok(Some(Box::new(v))),+            _ => unreachable!(),+        }+    }+     /// Parses `from 'foo.js' with {};` or `from 'foo.js' assert {};`     fn parse_from_clause_and_semi(&mut self) -> PResult<(Box<Str>, Option<Box<ObjectLit>>)> {         expect!(self, Token::From);@@ -29,17 +57,7 @@ impl<I: Tokens> Parser<I> {         } else {             unexpected!(self, "a string literal")         };-        let with = if self.input().syntax().import_attributes()-            && !self.input().had_line_break_before_cur()-            && (self.input_mut().eat(Token::Assert) || self.input_mut().eat(Token::With))-        {-            match self.parse_object_expr()? {-                Expr::Object(v) => Some(Box::new(v)),-                _ => unreachable!(),-            }-        } else {-            None-        };+        let with = self.parse_import_attributes()?;         self.expect_general_semi()?;         Ok((src, with))     }@@ -58,61 +76,61 @@ impl<I: Tokens> Parser<I> {                 // `export { type as }`                 // `export { type as as }`                 // `export { type as as as }`+                // These names may also be string ModuleExportNames.                 if self.syntax().typescript()                     && orig_token == Token::Type-                    && self.input().cur().is_word()+                    && (self.input().cur().is_word() || self.input().cur() == Token::Str)                 {                     let possibly_orig_token = self.input().cur();-                    let possibly_orig = self.parse_ident_name().map(Ident::from)?;+                    let possibly_orig = self.parse_module_export_name()?;                     if possibly_orig_token == Token::As {                         // `export { type as }`-                        if !self.input().cur().is_word() {+                        if !(self.input().cur().is_word() || self.input().cur() == Token::Str) {                             if type_only {                                 self.emit_err(orig_ident.span, SyntaxError::TS2207);                             }                              return Ok(ExportNamedSpecifier {                                 span: self.span(start),-                                orig: ModuleExportName::Ident(possibly_orig),+                                orig: possibly_orig,                                 exported: None,                                 is_type_only: true,                             });                         }                          let maybe_as_token = self.input().cur();-                        let maybe_as = self.parse_ident_name().map(Ident::from)?;+                        let maybe_as = self.parse_module_export_name()?;                         if maybe_as_token == Token::As {-                            if self.input().cur().is_word() {+                            if self.input().cur().is_word() || self.input().cur() == Token::Str {                                 // `export { type as as as }`                                 // `export { type as as foo }`-                                let exported = self.parse_ident_name().map(Ident::from)?;+                                let exported = self.parse_module_export_name()?;                                  if type_only {                                     self.emit_err(orig_ident.span, SyntaxError::TS2207);                                 } -                                debug_assert!(start <= orig_ident.span.hi());                                 return Ok(ExportNamedSpecifier {-                                    span: Span::new_with_checked(start, orig_ident.span.hi()),-                                    orig: ModuleExportName::Ident(possibly_orig),-                                    exported: Some(ModuleExportName::Ident(exported)),+                                    span: self.span(start),+                                    orig: possibly_orig,+                                    exported: Some(exported),                                     is_type_only: true,                                 });                             } else {                                 // `export { type as as }`                                 return Ok(ExportNamedSpecifier {-                                    span: Span::new_with_checked(start, orig_ident.span.hi()),+                                    span: self.span(start),                                     orig: ModuleExportName::Ident(orig_ident),-                                    exported: Some(ModuleExportName::Ident(maybe_as)),+                                    exported: Some(maybe_as),                                     is_type_only: false,                                 });                             }                         } else {                             // `export { type as xxx }`                             return Ok(ExportNamedSpecifier {-                                span: Span::new_with_checked(start, orig_ident.span.hi()),+                                span: self.span(start),                                 orig: ModuleExportName::Ident(orig_ident),-                                exported: Some(ModuleExportName::Ident(maybe_as)),+                                exported: Some(maybe_as),                                 is_type_only: false,                             });                         }@@ -124,7 +142,7 @@ impl<I: Tokens> Parser<I> {                         }                          is_type_only = true;-                        ModuleExportName::Ident(possibly_orig)+                        possibly_orig                     }                 } else {                     ModuleExportName::Ident(orig_ident)@@ -148,11 +166,11 @@ impl<I: Tokens> Parser<I> {     }      fn parse_imported_binding(&mut self) -> PResult<Ident> {-        Ok(self-            .do_outside_of_context(Context::InAsync.union(Context::InGenerator), |p| {-                p.parse_binding_ident(false)-            })?-            .into())+        // ImportedBinding : BindingIdentifier[~Yield, ~Await].+        let binding = with_grammar_context!(self, [~Yield, ~Await], |p| {+            p.parse_binding_ident(false)+        })?;+        Ok(binding.into())     }      fn parse_imported_default_binding(&mut self) -> PResult<Ident> {@@ -161,6 +179,7 @@ impl<I: Tokens> Parser<I> {      /// Parse `foo`, `foo2 as bar` in `import { foo, foo2 as bar }`     fn parse_import_specifier(&mut self, type_only: bool) -> PResult<ImportSpecifier> {+        let mut escaped_keyword_error = self.input().escaped_keyword_error();         let start = self.cur_pos();         let orig_token = self.input().cur();         match self.parse_module_export_name()? {@@ -181,10 +200,14 @@ impl<I: Tokens> Parser<I> {                         && orig_token == Token::TypeOf                         && self.input().cur().is_word()                     {+                        let escaped_keyword_error = self.input().escaped_keyword_error();                         let imported = self.parse_module_export_name()?;                         let local = if self.input_mut().eat(Token::As) {                             self.parse_binding_ident(false)?.into()                         } else {+                            if let Some(error) = escaped_keyword_error {+                                self.emit_error(error);+                            }                             match &imported {                                 ModuleExportName::Ident(i) => i.clone(),                                 ModuleExportName::Str(s) => {@@ -250,11 +273,12 @@ impl<I: Tokens> Parser<I> {                      if self.input().cur().is_word() {                         let possibly_orig_token = self.input().cur();+                        escaped_keyword_error = self.input().escaped_keyword_error();                         let possibly_orig_name = self.parse_ident_name().map(Ident::from)?;                         if possibly_orig_token == Token::As {                             // `import { type as } from 'mod'`                             if !self.input().cur().is_word() {-                                if self.ctx().is_reserved_word(&possibly_orig_name.sym) {+                                if self.word_is_reserved(&possibly_orig_name.sym) {                                     syntax_error!(                                         self,                                         possibly_orig_name.span,@@ -336,6 +360,10 @@ impl<I: Tokens> Parser<I> {                     }));                 } +                if let Some(error) = escaped_keyword_error {+                    self.emit_error(error);+                }+                 // Handle difference between                 //                 // 'ImportedBinding'@@ -343,7 +371,7 @@ impl<I: Tokens> Parser<I> {                 if self.input().syntax().flow() && orig_name.sym == *"default" {                     self.emit_err(orig_name.span, SyntaxError::TS1003);                 }-                if self.ctx().is_reserved_word(&orig_name.sym)+                if self.word_is_reserved(&orig_name.sym)                     && !(self.input().syntax().flow() && (type_only || is_type_only))                 {                     syntax_error!(self, orig_name.span, SyntaxError::ReservedWordInImport)@@ -383,7 +411,9 @@ impl<I: Tokens> Parser<I> {     }      pub(crate) fn parse_export(&mut self, mut decorators: Vec<Decorator>) -> PResult<ModuleDecl> {-        if !self.ctx().contains(Context::Module) && self.ctx().contains(Context::TopLevel) {+        if !self.ctx().contains(Context::Module)+            && self.boundary_ctx().contains(BoundaryContext::TopLevel)+        {             // Switch to module mode             let ctx = self.ctx() | Context::Module | Context::Strict;             self.set_ctx(ctx);@@ -600,7 +630,7 @@ impl<I: Tokens> Parser<I> {                     self.input().prev_span(),                 ))             } else {-                let expr = self.allow_in_expr(Self::parse_assignment_expr)?;+                let expr = with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;                 self.expect_general_semi()?;                 return Ok(ExportDefaultExpr {                     span: self.span(start),@@ -905,17 +935,7 @@ impl<I: Tokens> Parser<I> {         if self.input().cur() == Token::Str {             self.enter_import_module_context();             let src = Box::new(self.parse_str_lit());-            let with = if self.input().syntax().import_attributes()-                && !self.input().had_line_break_before_cur()-                && (self.input_mut().eat(Token::Assert) || self.input_mut().eat(Token::With))-            {-                match self.parse_object_expr()? {-                    Expr::Object(v) => Some(Box::new(v)),-                    _ => unreachable!(),-                }-            } else {-                None-            };+            let with = self.parse_import_attributes()?;             self.eat_general_semi();             return Ok(ImportDecl {                 span: self.span(start),@@ -1073,17 +1093,7 @@ impl<I: Tokens> Parser<I> {             }         }; -        let with = if self.input().syntax().import_attributes()-            && !self.input().had_line_break_before_cur()-            && (self.input_mut().eat(Token::Assert) || self.input_mut().eat(Token::With))-        {-            match self.parse_object_expr()? {-                Expr::Object(v) => Some(Box::new(v)),-                _ => unreachable!(),-            }-        } else {-            None-        };+        let with = self.parse_import_attributes()?;          self.expect_general_semi()?; @@ -1103,9 +1113,8 @@ fn handle_import_export<I: Tokens>(     p: &mut Parser<I>,     decorators: Vec<Decorator>, ) -> PResult<ModuleItem> {-    if !p-        .ctx()-        .intersects(Context::TopLevel.union(Context::TsModuleBlock))+    if !p.boundary_ctx().contains(BoundaryContext::TopLevel)+        && !p.type_ctx().contains(TypeContext::TsModuleBlock)     {         syntax_error!(p, SyntaxError::NonTopLevelImportExport);     }

deps/swc/crates/swc_ecma_parser/src/parser/object.rs

@@ -1,7 +1,13 @@ use swc_common::{Span, Spanned}; use swc_ecma_ast::*; -use crate::{error::SyntaxError, input::Tokens, lexer::Token, Context, PResult, Parser};+use crate::{+    error::SyntaxError,+    input::Tokens,+    lexer::Token,+    parser::{BoundaryContext, TypeContext},+    Context, PResult, Parser,+};  fn prop_name_is(key: &PropName, expected: &str) -> bool {     match key {@@ -17,7 +23,7 @@ impl<I: Tokens> Parser<I> {         parse_prop: impl Fn(&mut Self) -> PResult<ObjectProp>,         make_object: impl Fn(&mut Self, Span, Vec<ObjectProp>, Option<Span>) -> PResult<Object>,     ) -> PResult<Object> {-        self.do_outside_of_context(Context::WillExpectColonForCond, |p| {+        self.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {             trace_cur!(p, parse_object);              let start = p.cur_pos();@@ -60,6 +66,7 @@ impl<I: Tokens> Parser<I> {             }));         } +        let escaped_keyword_error = self.escaped_keyword_error();         let key = self.parse_prop_name()?;         if self.input_mut().eat(Token::Colon) {             let value = Box::new(self.parse_binding_element()?);@@ -71,11 +78,16 @@ impl<I: Tokens> Parser<I> {             _ => unexpected!(self, "an identifier"),         }; +        // A shorthand key is a binding identifier even when it has a default.+        if let Some(error) = escaped_keyword_error {+            self.emit_error(error);+        }         let value = if self.input_mut().eat(Token::Eq) {-            self.allow_in_expr(Self::parse_assignment_expr).map(Some)?+            with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)+                .map(Some)?         } else {             let ctx = self.ctx();-            if self.ctx().is_reserved_word(&key.sym) {+            if self.word_is_reserved(&key.sym) {                 self.emit_err(key.span, SyntaxError::ReservedWordInObjShorthandOrPat);             } @@ -85,15 +97,13 @@ impl<I: Tokens> Parser<I> {                         self.emit_err(key.span, SyntaxError::EvalAndArgumentsInStrict);                     }                     "await"-                        if ctx.contains(Context::InAsync)-                            || ctx.contains(Context::InStaticBlock)+                        if self.includes_await_expr()+                            || self.boundary_ctx().contains(BoundaryContext::InStaticBlock)                             || ctx.contains(Context::Module) =>                     {                         self.emit_err(key.span, SyntaxError::InvalidIdentInAsync);                     }-                    "yield"-                        if ctx.contains(Context::InGenerator) || ctx.contains(Context::Strict) =>-                    {+                    "yield" if self.includes_yield_expr() || ctx.contains(Context::Strict) => {                         self.emit_err(key.span, SyntaxError::InvalidIdentInStrict(key.sym.clone()));                     }                     "implements" | "interface" | "package" | "private" | "protected" | "public"@@ -143,7 +153,8 @@ impl<I: Tokens> Parser<I> {             }         } -        let optional = (self.input().syntax().dts() || self.ctx().contains(Context::InDeclare))+        let optional = (self.input().syntax().dts()+            || self.type_ctx().contains(TypeContext::InDeclare))             && self.input_mut().eat(Token::QuestionMark);          Ok(ObjectPat {@@ -166,9 +177,13 @@ impl<I: Tokens> Parser<I> {         trailing_comma: Option<Span>,     ) -> PResult<Expr> {         if let Some(trailing_comma) = trailing_comma {-            self.state_mut()-                .trailing_commas-                .insert(span.lo, trailing_comma);+            // Preserve the comma only if cover grammar can turn the last property into+            // rest.+            if matches!(props.last(), Some(PropOrSpread::Spread(_))) {+                self.state_mut()+                    .trailing_commas+                    .insert(span.lo, trailing_comma);+            }         }         Ok(ObjectLit { span, props }.into())     }@@ -183,7 +198,8 @@ impl<I: Tokens> Parser<I> {             // spread element             let dot3_token = self.span(start); -            let expr = self.allow_in_expr(Self::parse_assignment_expr)?;+            let expr =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;              return Ok(PropOrSpread::Spread(SpreadElement { dot3_token, expr }));         }@@ -191,8 +207,8 @@ impl<I: Tokens> Parser<I> {         if self.input_mut().eat(Token::Asterisk) {             let name = self.parse_prop_name()?;             return self-                .do_inside_of_context(Context::AllowDirectSuper, |p| {-                    p.do_outside_of_context(Context::InClassField, |p| {+                .do_inside_of_boundary_context(BoundaryContext::AllowDirectSuper, |p| {+                    p.do_outside_of_boundary_context(BoundaryContext::InClassField, |p| {                         p.parse_fn_args_body(                             // no decorator in an object literal                             Vec::new(),@@ -215,6 +231,7 @@ impl<I: Tokens> Parser<I> {         let modifiers_span = self.input().prev_span();          let key_token = self.input().cur();+        let escaped_keyword_error = self.escaped_keyword_error();         let key = self.parse_prop_name()?;          let cur = self.input().cur();@@ -251,7 +268,8 @@ impl<I: Tokens> Parser<I> {         // { 0: 1, }         // { a: expr, }         if self.input_mut().eat(Token::Colon) {-            let value = self.allow_in_expr(Self::parse_assignment_expr)?;+            let value =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;             return Ok(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {                 key,                 value,@@ -274,8 +292,8 @@ impl<I: Tokens> Parser<I> {             }              return self-                .do_inside_of_context(Context::AllowDirectSuper, |p| {-                    p.do_outside_of_context(Context::InClassField, |p| {+                .do_inside_of_boundary_context(BoundaryContext::AllowDirectSuper, |p| {+                    p.do_outside_of_boundary_context(BoundaryContext::InClassField, |p| {                         p.parse_fn_args_body(                             // no decorator in an object literal                             Vec::new(),@@ -304,8 +322,11 @@ impl<I: Tokens> Parser<I> {         // It means we should check for invalid expressions like { for, }         let cur = self.input().cur();         if matches!(cur, Token::Eq | Token::Comma | Token::RBrace) {+            if let Some(error) = escaped_keyword_error {+                self.emit_error(error);+            }             let ctx = self.ctx();-            if self.ctx().is_reserved_word(&ident.sym) {+            if self.word_is_reserved(&ident.sym) {                 self.emit_err(ident.span, SyntaxError::ReservedWordInObjShorthandOrPat);             } @@ -315,15 +336,13 @@ impl<I: Tokens> Parser<I> {                         self.emit_err(ident.span, SyntaxError::EvalAndArgumentsInStrict);                     }                     "await"-                        if ctx.contains(Context::InAsync)-                            || ctx.contains(Context::InStaticBlock)+                        if self.includes_await_expr()+                            || self.boundary_ctx().contains(BoundaryContext::InStaticBlock)                             || ctx.contains(Context::Module) =>                     {                         self.emit_err(ident.span, SyntaxError::InvalidIdentInAsync);                     }-                    "yield"-                        if ctx.contains(Context::InGenerator) || ctx.contains(Context::Strict) =>-                    {+                    "yield" if self.includes_yield_expr() || ctx.contains(Context::Strict) => {                         self.emit_err(                             ident.span,                             SyntaxError::InvalidIdentInStrict(ident.sym.clone()),@@ -343,7 +362,7 @@ impl<I: Tokens> Parser<I> {             }              if self.input_mut().eat(Token::Eq) {-                let value = self.allow_in_expr(Self::parse_assignment_expr)?;+                let value = with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;                 let span = self.span(start);                 return Ok(PropOrSpread::Prop(Box::new(Prop::Assign(AssignProp {                     span,@@ -374,8 +393,8 @@ impl<I: Tokens> Parser<I> {                 if matches!(key_token, Token::Get | Token::Set) && self.input().is(Token::Lt) {                     self.emit_err(self.input().cur_span(), SyntaxError::TS1003);                 }-                self.do_inside_of_context(Context::AllowDirectSuper, |p| {-                    p.do_outside_of_context(Context::InClassField, |p| {+                self.do_inside_of_boundary_context(BoundaryContext::AllowDirectSuper, |p| {+                    p.do_outside_of_boundary_context(BoundaryContext::InClassField, |p| {                         match key_token {                             Token::Get => p                                 .parse_fn_args_body(

deps/swc/crates/swc_ecma_parser/src/parser/pat.rs

@@ -49,6 +49,90 @@ fn direct_ts_this_ident(pat: &Pat) -> Option<&Ident> {     Some(ident) } +/// The binding information needed by shared parameter-list validation.+/// Implementations borrow it from the final AST instead of retaining a+/// second list of intermediate patterns.+pub(super) trait FormalParameter: ParameterListItem {+    fn binding_ident(&self) -> Option<&BindingIdent>;++    fn direct_this_ident(&self) -> Option<&Ident>;+}++/// The list grammar only needs to know whether an item is a rest parameter.+pub(super) trait ParameterListItem {+    fn rest_span(&self) -> Option<Span>;+}++impl ParameterListItem for Param {+    fn rest_span(&self) -> Option<Span> {+        self.pat.rest_span()+    }+}++impl ParameterListItem for Pat {+    fn rest_span(&self) -> Option<Span> {+        match self {+            Pat::Rest(rest) => Some(rest.span),+            _ => None,+        }+    }+}++impl FormalParameter for Param {+    fn binding_ident(&self) -> Option<&BindingIdent> {+        self.pat.binding_ident()+    }++    fn direct_this_ident(&self) -> Option<&Ident> {+        self.pat.direct_this_ident()+    }+}++impl FormalParameter for Pat {+    fn binding_ident(&self) -> Option<&BindingIdent> {+        self.as_ident()+    }++    fn direct_this_ident(&self) -> Option<&Ident> {+        direct_ts_this_ident(self)+    }+}++impl ParameterListItem for TsFnParam {+    fn rest_span(&self) -> Option<Span> {+        match self {+            Self::Rest(rest) => Some(rest.span),+            _ => None,+        }+    }+}++impl ParameterListItem for ParamOrTsParamProp {+    fn rest_span(&self) -> Option<Span> {+        match self {+            Self::Param(param) => param.rest_span(),+            Self::TsParamProp(_) => None,+            #[cfg(swc_ast_unknown)]+            _ => unreachable!("parser constructs only known parameter variants"),+        }+    }+}++impl FormalParameter for TsFnParam {+    fn binding_ident(&self) -> Option<&BindingIdent> {+        match self {+            Self::Ident(ident) => Some(ident),+            _ => None,+        }+    }++    fn direct_this_ident(&self) -> Option<&Ident> {+        self.binding_ident()+            .map(|ident| &ident.id)+            .filter(|ident| &*ident.sym == "this")+    }+}+ impl<I: Tokens> Parser<I> {     pub fn parse_pat(&mut self) -> PResult<Pat> {         self.parse_binding_pat_or_ident(false)@@ -363,55 +447,28 @@ impl<I: Tokens> Parser<I> {                 .into())             }             Expr::Ident(ident) => Ok(ident.into()),-            Expr::Array(ArrayLit {-                elems: mut exprs, ..-            }) => {-                if exprs.is_empty() {-                    return Ok(ArrayPat {-                        span,-                        elems: Vec::new(),-                        optional: false,-                        type_ann: None,-                    }-                    .into());-                }-                // Trailing comma may exist. We should remove those commas.-                let count_of_trailing_comma =-                    exprs.iter().rev().take_while(|e| e.is_none()).count();+            Expr::Array(ArrayLit { elems: exprs, .. }) => {                 let len = exprs.len();-                let mut params = Vec::with_capacity(exprs.len() - count_of_trailing_comma);-                // Comma or other pattern cannot follow a rest pattern.-                let idx_of_rest_not_allowed = if count_of_trailing_comma == 0 {-                    len - 1-                } else {-                    // last element is comma, so rest is not allowed for every pattern element.-                    len - count_of_trailing_comma-                };-                for expr in exprs.drain(..idx_of_rest_not_allowed) {-                    match expr {+                let mut params = Vec::with_capacity(len);+                // Array literals do not store a trailing comma as an element. Every `None`,+                // including a trailing one, is an elision that advances the iterator in both+                // binding and assignment patterns.+                for (idx, expr) in exprs.into_iter().enumerate() {+                    let pat = match expr {                         Some(                             expr @ ExprOrSpread {                                 spread: Some(..), ..                             },-                        ) => self.emit_err(expr.span(), SyntaxError::NonLastRestParam),-                        Some(ExprOrSpread { expr, .. }) => {-                            params.push(self.reparse_expr_as_pat(pat_ty.element(), expr).map(Some)?)+                        ) if idx + 1 != len => {+                            self.emit_err(expr.span(), SyntaxError::NonLastRestParam);+                            continue;                         }-                        None => params.push(None),-                    }-                }-                if count_of_trailing_comma == 0 {-                    let expr = exprs.into_iter().next().unwrap();-                    let outer_expr_span = expr.span();-                    let last = match expr {-                        // Rest                         Some(ExprOrSpread {                             spread: Some(dot3_token),                             expr,                         }) => {-                            // TODO: is BindingPat correct?                             if let Expr::Assign(_) = *expr {-                                self.emit_err(outer_expr_span, SyntaxError::TS1048);+                                self.emit_err(dot3_token.to(expr.span()), SyntaxError::TS1048);                             };                             if let Some(trailing_comma) = self.state().trailing_commas.get(&span.lo)                             {@@ -431,13 +488,11 @@ impl<I: Tokens> Parser<I> {                                 .map(Some)?                         }                         Some(ExprOrSpread { expr, .. }) => {-                            // TODO: is BindingPat correct?                             self.reparse_expr_as_pat(pat_ty.element(), expr).map(Some)?                         }-                        // TODO: syntax error if last element is ellison and ...rest exists.                         None => None,                     };-                    params.push(last);+                    params.push(pat);                 }                 Ok(ArrayPat {                     span,@@ -455,7 +510,7 @@ impl<I: Tokens> Parser<I> {                 Ok(Invalid { span }.into())             } -            Expr::Yield(..) if self.ctx().contains(Context::InGenerator) => {+            Expr::Yield(..) if self.includes_yield_expr() => {                 self.emit_err(span, SyntaxError::InvalidPat);                 Ok(Invalid { span }.into())             }@@ -484,16 +539,17 @@ impl<I: Tokens> Parser<I> {         if self.input_mut().eat(Token::Eq) {             let initializer_span = self.input().prev_span(); -            let right = self.allow_in_expr(Self::parse_assignment_expr)?;+            let right =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?; -            if self.ctx().contains(Context::InParameters)-                && self.ctx().contains(Context::InAsync)+            if self.boundary_ctx().contains(BoundaryContext::InParameters)+                && self.includes_await_expr()                 && is_await_ident_or_expr(&right)             {                 self.emit_err(right.span(), SyntaxError::AwaitParamInAsync);             } -            if self.ctx().contains(Context::InDeclare) {+            if self.type_ctx().contains(TypeContext::InDeclare) {                 self.emit_err(self.span(start), SyntaxError::TS2371);             } @@ -578,7 +634,8 @@ impl<I: Tokens> Parser<I> {         }          expect!(self, Token::RBracket);-        let optional = (self.input().syntax().dts() || self.ctx().contains(Context::InDeclare))+        let optional = (self.input().syntax().dts()+            || self.type_ctx().contains(TypeContext::InDeclare))             && self.input_mut().eat(Token::QuestionMark);          Ok(ArrayPat {@@ -637,8 +694,8 @@ impl<I: Tokens> Parser<I> {                         *optional = true;                         opt = true;                     }-                    _ if self.input().syntax().dts() || self.ctx().contains(Context::InDeclare) => {-                    }+                    _ if self.input().syntax().dts()+                        || self.type_ctx().contains(TypeContext::InDeclare) => {}                     _ => {                         syntax_error!(                             self,@@ -704,13 +761,13 @@ impl<I: Tokens> Parser<I> {             }              let right = self.parse_assignment_expr()?;-            if self.ctx().contains(Context::InParameters)-                && self.ctx().contains(Context::InAsync)+            if self.boundary_ctx().contains(BoundaryContext::InParameters)+                && self.includes_await_expr()                 && is_await_ident_or_expr(&right)             {                 self.emit_err(right.span(), SyntaxError::AwaitParamInAsync);             }-            if self.ctx().contains(Context::InDeclare) {+            if self.type_ctx().contains(TypeContext::InDeclare) {                 self.emit_err(self.span(start), SyntaxError::TS2371);             } @@ -772,33 +829,32 @@ impl<I: Tokens> Parser<I> {     }      pub(crate) fn parse_constructor_params(&mut self) -> PResult<Vec<ParamOrTsParamProp>> {-        let mut params = Vec::new();-        let mut rest_span = Span::default();--        while !self.input().is(Token::RParen) {-            if !rest_span.is_dummy() {-                self.emit_err(rest_span, SyntaxError::TS1014);-            }+        self.do_inside_of_boundary_context(BoundaryContext::InParameters, |p| {+            p.with_syntax_context(+                super::SyntaxContext::Parameters,+                Self::parse_constructor_params_inner,+            )+        })+    } -            let param_start = self.cur_pos();-            let decorators = self.parse_decorators(false)?;-            let pat_start = self.cur_pos();+    fn parse_constructor_params_inner(&mut self) -> PResult<Vec<ParamOrTsParamProp>> {+        self.parse_parameter_list(|p, _| {+            let param_start = p.cur_pos();+            let decorators = p.parse_decorators(false)?;+            let pat_start = p.cur_pos(); -            let mut is_rest = false;-            if self.input_mut().eat(Token::DotDotDot) {-                is_rest = true;-                let dot3_token = self.span(pat_start);+            if p.input_mut().eat(Token::DotDotDot) {+                let dot3_token = p.span(pat_start); -                let pat = self.parse_binding_pat_or_ident(false)?;-                let type_ann =-                    if self.input().syntax().typescript() && self.input().is(Token::Colon) {-                        let cur_pos = self.cur_pos();-                        Some(self.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?)-                    } else {-                        None-                    };+                let pat = p.parse_binding_pat_or_ident(false)?;+                let type_ann = if p.input().syntax().typescript() && p.input().is(Token::Colon) {+                    let cur_pos = p.cur_pos();+                    Some(p.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?)+                } else {+                    None+                }; -                rest_span = self.span(pat_start);+                let rest_span = p.span(pat_start);                 let pat = RestPat {                     span: rest_span,                     dot3_token,@@ -806,117 +862,80 @@ impl<I: Tokens> Parser<I> {                     type_ann,                 }                 .into();-                params.push(ParamOrTsParamProp::Param(Param {-                    span: self.span(param_start),+                Ok(ParamOrTsParamProp::Param(Param {+                    span: p.span(param_start),                     decorators,                     pat,-                }));+                }))             } else {-                params.push(self.parse_constructor_param(param_start, decorators)?);+                p.parse_constructor_param(param_start, decorators)             }--            if !self.input().is(Token::RParen) {-                expect!(self, Token::Comma);-                if self.input().is(Token::RParen) && is_rest {-                    self.emit_err(self.input().prev_span(), SyntaxError::CommaAfterRestElement);-                }-            }-        }--        Ok(params)+        })     }      pub(crate) fn parse_formal_params(&mut self) -> PResult<Vec<Param>> {-        let mut params = Vec::new();-        let mut rest_span = Span::default();--        while !self.input().is(Token::RParen) {-            if !rest_span.is_dummy() {-                self.emit_err(rest_span, SyntaxError::TS1014);-            }--            let param_start = self.cur_pos();-            let decorators = self.parse_decorators(false)?;-            let pat_start = self.cur_pos();--            let pat = if let Some(pat) = self.try_parse_flow_anon_formal_param(params.len())? {-                pat-            } else if self.input_mut().eat(Token::DotDotDot) {-                let dot3_token = self.span(pat_start);--                let mut pat = self.parse_binding_pat_or_ident(false)?;--                if self.input_mut().eat(Token::Eq) {-                    let right = self.parse_assignment_expr()?;-                    self.emit_err(pat.span(), SyntaxError::TS1048);-                    pat = AssignPat {-                        span: self.span(pat_start),-                        left: Box::new(pat),-                        right,-                    }-                    .into();-                }--                let type_ann =-                    if self.input().syntax().typescript() && self.input().is(Token::Colon) {-                        let cur_pos = self.cur_pos();-                        let ty = self.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?;-                        Some(ty)-                    } else {-                        None-                    };+        self.parse_formal_params_with(|_, param| Ok(param))+    } -                rest_span = self.span(pat_start);-                let pat = RestPat {-                    span: rest_span,-                    dot3_token,-                    arg: Box::new(pat),-                    type_ann,-                }-                .into();+    /// Arrow ASTs retain patterns without parameter decorator wrappers.+    #[cfg(feature = "typescript")]+    pub(super) fn parse_formal_param_patterns(&mut self) -> PResult<Vec<Pat>> {+        self.parse_formal_params_with(|_, param| Ok(param.pat))+    } -                if self.syntax().typescript() && self.input_mut().eat(Token::QuestionMark) {-                    self.emit_err(self.input().prev_span(), SyntaxError::TS1047);-                    //-                }+    fn parse_formal_params_with<T: FormalParameter>(+        &mut self,+        convert: impl FnMut(&mut Self, Param) -> PResult<T>,+    ) -> PResult<Vec<T>> {+        self.do_inside_of_boundary_context(BoundaryContext::InParameters, |p| {+            p.with_syntax_context(super::SyntaxContext::Parameters, |p| {+                p.parse_formal_params_inner(convert)+            })+        })+    } +    /// Shares parameter grammar while constructing the consumer's final+    /// representation.+    pub(super) fn parse_formal_params_inner<T: FormalParameter>(+        &mut self,+        mut convert: impl FnMut(&mut Self, Param) -> PResult<T>,+    ) -> PResult<Vec<T>> {+        let params = self.parse_parameter_list(|p, index| {+            // Construct and convert in the same production so pattern-only+            // consumers do not carry a separate parameter return value.+            let param_start = p.cur_pos();+            let decorators = p.parse_decorators(false)?;+            let pat_start = p.cur_pos();++            let pat = if let Some(pat) = p.try_parse_flow_anon_formal_param(index)? {                 pat+            } else if p.input_mut().eat(Token::DotDotDot) {+                p.parse_rest_parameter(pat_start)?             } else {-                self.parse_formal_param_pat()?+                p.parse_formal_param_pat()?             };-            let is_rest = matches!(pat, Pat::Rest(_));-            if is_rest {-                rest_span = pat.span();-            }--            params.push(Param {-                span: self.span(param_start),+            let param = Param {+                span: p.span(param_start),                 decorators,                 pat,-            });--            if !self.input().is(Token::RParen) {-                expect!(self, Token::Comma);-                if is_rest && self.input().is(Token::RParen) {-                    self.emit_err(self.input().prev_span(), SyntaxError::CommaAfterRestElement);-                }-            }-        }+            };+            convert(p, param)+        })?;          if self.syntax().typescript() && !self.syntax().flow() {             for param in params.iter().skip(1) {-                let Some(ident) = direct_ts_this_ident(&param.pat) else {+                let Some(ident) = param.direct_this_ident() else {                     continue;                 };                 self.emit_err(ident.span, SyntaxError::TS2680);             }         } -        if self.syntax().flow() && !self.ctx().contains(Context::InType) {-            let in_declare = self.ctx().contains(Context::InDeclare);+        if self.syntax().flow() && !self.type_ctx().contains(TypeContext::InType) {+            let in_declare = self.type_ctx().contains(TypeContext::InDeclare);              for (idx, param) in params.iter().enumerate() {-                if let Pat::Ident(ident) = &param.pat {+                if let Some(ident) = param.binding_ident() {                     if ident.id.sym == *"this" {                         if idx != 0 {                             self.emit_err(ident.id.span, SyntaxError::TS1003);@@ -935,6 +954,85 @@ impl<I: Tokens> Parser<I> {         Ok(params)     } +    /// Parses a parameter list into its final AST representation. Individual+    /// parameter grammar and list-wide binding validation remain with callers.+    pub(super) fn parse_parameter_list<T: ParameterListItem>(+        &mut self,+        mut parse: impl FnMut(&mut Self, usize) -> PResult<T>,+    ) -> PResult<Vec<T>> {+        let mut params = Vec::new();+        let mut rest_span = Span::default();++        while !self.input().is(Token::RParen) {+            if !rest_span.is_dummy() {+                self.emit_err(rest_span, SyntaxError::TS1014);+            }++            let param = parse(self, params.len())?;+            let current_rest = param.rest_span();+            if let Some(span) = current_rest {+                rest_span = span;+            }+            params.push(param);++            if !self.input().is(Token::RParen) {+                expect!(self, Token::Comma);+                // Ambient TypeScript signatures allow a trailing comma after rest.+                if current_rest.is_some()+                    && self.input().is(Token::RParen)+                    && (!self.type_ctx().contains(TypeContext::InDeclare) || self.syntax().flow())+                {+                    self.emit_err(self.input().prev_span(), SyntaxError::CommaAfterRestElement);+                }+            }+        }++        Ok(params)+    }++    /// Parses the binding and annotation after an already consumed `...`.+    /// List-level rest placement and trailing-comma checks belong to the+    /// caller.+    pub(super) fn parse_rest_parameter(&mut self, pat_start: BytePos) -> PResult<Pat> {+        let dot3_token = self.span(pat_start);++        let mut pat = self.parse_binding_pat_or_ident(false)?;++        if self.input_mut().eat(Token::Eq) {+            let right = self.parse_assignment_expr()?;+            self.emit_err(pat.span(), SyntaxError::TS1048);+            pat = AssignPat {+                span: self.span(pat_start),+                left: Box::new(pat),+                right,+            }+            .into();+        }++        let type_ann = if self.input().syntax().typescript() && self.input().is(Token::Colon) {+            let cur_pos = self.cur_pos();+            let ty = self.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?;+            Some(ty)+        } else {+            None+        };++        let rest_span = self.span(pat_start);+        let pat = RestPat {+            span: rest_span,+            dot3_token,+            arg: Box::new(pat),+            type_ann,+        }+        .into();++        if self.syntax().typescript() && self.input_mut().eat(Token::QuestionMark) {+            self.emit_err(self.input().prev_span(), SyntaxError::TS1047);+        }++        Ok(pat)+    }+     pub(crate) fn parse_unique_formal_params(&mut self) -> PResult<Vec<Param>> {         // FIXME: This is wrong         self.parse_formal_params()

deps/swc/crates/swc_ecma_parser/src/parser/stmt.rs

@@ -67,7 +67,8 @@ impl<I: Tokens> Parser<I> {         let test = if self.input_mut().eat(Token::Semi) {             None         } else {-            let test = self.allow_in_expr(|p| p.parse_expr()).map(Some)?;+            let test =+                with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr()).map(Some)?;             self.input_mut().eat(Token::Semi);             test         };@@ -75,7 +76,7 @@ impl<I: Tokens> Parser<I> {         let update = if self.input().is(Token::RParen) {             None         } else {-            self.allow_in_expr(|p| p.parse_expr()).map(Some)?+            with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr()).map(Some)?         };          Ok(TempForHead::For { init, test, update })@@ -85,13 +86,14 @@ impl<I: Tokens> Parser<I> {         let is_of = self.input().cur() == Token::Of;         self.bump();         if is_of {-            let right = self.allow_in_expr(Self::parse_assignment_expr)?;+            let right =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;             Ok(TempForHead::ForOf { left, right })         } else {             if let ForHead::UsingDecl(d) = &left {                 self.emit_err(d.span, SyntaxError::UsingDeclNotAllowedForForInLoop)             }-            let right = self.allow_in_expr(|p| p.parse_expr())?;+            let right = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;             Ok(TempForHead::ForIn { left, right })         }     }@@ -103,7 +105,7 @@ impl<I: Tokens> Parser<I> {         let arg = if self.is_general_semi() {             None         } else {-            self.allow_in_expr(|p| p.parse_expr()).map(Some)?+            with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr()).map(Some)?         };         self.expect_general_semi()?;         let stmt = Ok(ReturnStmt {@@ -112,9 +114,7 @@ impl<I: Tokens> Parser<I> {         }         .into()); -        if !self.ctx().contains(Context::InFunction)-            && !self.input().syntax().allow_return_outside_function()-        {+        if !self.allows_return_stmt() && !self.input().syntax().allow_return_outside_function() {             self.emit_err(self.span(start), SyntaxError::ReturnNotAllowed);         } @@ -135,7 +135,8 @@ impl<I: Tokens> Parser<I> {         let is_typescript =             matches!(cur, Token::Bang | Token::Colon) && self.input().syntax().typescript(); -        let definite = if is_typescript {+        // `!` cannot be a definite assignment assertion after a line break.+        let definite = if is_typescript && !self.input().had_line_break_before_cur() {             match name {                 Pat::Ident(..) => self.input_mut().eat(Token::Bang),                 _ => false,@@ -177,11 +178,11 @@ impl<I: Tokens> Parser<I> {             } else {                 // Destructuring bindings require initializers, but                 // typescript allows `declare` vars not to have initializers.-                if self.ctx().contains(Context::InDeclare) {+                if self.type_ctx().contains(TypeContext::InDeclare) {                     None                 } else if kind == VarDeclKind::Const                     && !for_loop-                    && !self.ctx().contains(Context::InDeclare)+                    && !self.type_ctx().contains(TypeContext::InDeclare)                 {                     self.emit_err(                         self.span(start),@@ -282,9 +283,7 @@ impl<I: Tokens> Parser<I> {             }              let decl = if should_include_in {-                self.do_inside_of_context(Context::IncludeInExpr, |p| {-                    p.parse_var_declarator(for_loop, kind)-                })+                with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_var_declarator(for_loop, kind))             } else {                 self.parse_var_declarator(for_loop, kind)             }?;@@ -360,7 +359,10 @@ impl<I: Tokens> Parser<I> {             self.emit_err(self.span(start), SyntaxError::UsingDeclNotEnabled);         } -        if !self.ctx().contains(Context::AllowUsingDecl) {+        if !self+            .statement_ctx()+            .contains(StatementContext::AllowUsingDecl)+        {             self.emit_err(self.span(start), SyntaxError::UsingDeclNotAllowed);         } @@ -387,12 +389,17 @@ impl<I: Tokens> Parser<I> {     }      pub(super) fn parse_for_head(&mut self) -> PResult<TempForHead> {-        // let strict = self.ctx().contains(Context::Strict);+        self.parse_for_head_with_await(false)+    } +    /// Keeps the iteration kind local to the loop head, not its nested+    /// expressions.+    pub(super) fn parse_for_head_with_await(&mut self, is_await: bool) -> PResult<TempForHead> {         let cur = self.input().cur();         if cur == Token::Const             || cur == Token::Var             || (self.input().is(Token::Let)+                && !self.input().has_escaped_keyword()                 && peek!(self).map_or(false, |v| v.follows_keyword_let()))         {             let decl = self.parse_var_stmt(true)?;@@ -452,7 +459,7 @@ impl<I: Tokens> Parser<I> {         }          let start = self.cur_pos();-        let init = self.disallow_in_expr(Self::parse_for_head_prefix)?;+        let init = with_grammar_context!(self, [~In, ?Yield, ?Await], Self::parse_for_head_prefix)?;          let mut is_using_decl = false;         let mut is_await_using_decl = false;@@ -474,7 +481,8 @@ impl<I: Tokens> Parser<I> {             }              if maybe_using_decl-                && !self.input().is(Token::Of)+                // Only synchronous `using` forbids `of` as a for-of binding name.+                && (maybe_await_using_decl || !self.input().is(Token::Of))                 && (peek!(self).is_some_and(|peek| peek == Token::Of || peek == Token::In))             {                 is_using_decl = maybe_using_decl;@@ -513,6 +521,16 @@ impl<I: Tokens> Parser<I> {         if cur == Token::Of || cur == Token::In {             let is_in = self.input().is(Token::In); +            // The lookahead restriction matches the literal `async of` tokens,+            // not an escaped identifier with the same decoded name.+            if !is_in+                && !is_await+                && init.is_ident_ref_to("async")+                && self.input.iter.read_string(init.span()) == "async"+            {+                self.emit_err(init.span(), SyntaxError::TS1106);+            }+             let pat = self.reparse_expr_as_pat(PatType::AssignPat, init)?;              // for ({} in foo) is invalid@@ -538,7 +556,9 @@ impl<I: Tokens> Parser<I> {          self.assert_and_bump(Token::For);         let await_start = self.cur_pos();-        let await_token = if self.input_mut().eat(Token::Await) {+        let await_token = if self.input().is(Token::Await) {+            self.check_current_token_escape()?;+            self.bump();             Some(self.span(await_start))         } else {             None@@ -548,20 +568,18 @@ impl<I: Tokens> Parser<I> {         }         expect!(self, Token::LParen); -        let head = self.do_inside_of_context(Context::ForLoopInit, |p| {-            if await_token.is_some() {-                p.do_inside_of_context(Context::ForAwaitLoopInit, Self::parse_for_head)-            } else {-                p.do_outside_of_context(Context::ForAwaitLoopInit, Self::parse_for_head)-            }-        })?;+        let head = if await_token.is_some() {+            self.parse_for_head_with_await(true)?+        } else {+            self.parse_for_head()?+        };          expect!(self, Token::RParen);          let body = self-            .do_inside_of_context(-                Context::IsBreakAllowed.union(Context::IsContinueAllowed),-                |p| p.do_outside_of_context(Context::TopLevel, Self::parse_stmt),+            .do_inside_of_statement_context(+                StatementContext::IsBreakAllowed.union(StatementContext::IsContinueAllowed),+                |p| p.do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt),             )             .map(Box::new)?; @@ -595,11 +613,12 @@ impl<I: Tokens> Parser<I> {                 .into()             }             TempForHead::ForOf { left, right } => {-                if await_token.is_some()-                    && self.syntax().flow()-                    && !self.ctx().contains(Context::InAsync)-                {-                    self.emit_err(self.span(start), SyntaxError::AwaitForStmt);+                if let Some(await_token) = await_token {+                    if self.is_await_forbidden_in_initializer() {+                        self.emit_err(await_token, SyntaxError::ExpectedIdent);+                    } else if self.syntax().flow() && !self.includes_await_expr() {+                        self.emit_err(self.span(start), SyntaxError::AwaitForStmt);+                    }                 }                  ForOfStmt {@@ -616,7 +635,7 @@ impl<I: Tokens> Parser<I> {      pub fn parse_stmt(&mut self) -> PResult<Stmt> {         trace_cur!(self, parse_stmt);-        self.parse_stmt_like(false, handle_import_export)+        self.parse_stmt_like(StatementGrammar::Statement, handle_import_export)     }      /// Utility function used to parse large if else statements iteratively.@@ -643,9 +662,10 @@ impl<I: Tokens> Parser<I> {         expect!(self, Token::LParen);          let test = self-            .do_outside_of_context(Context::IgnoreElseClause, |p| {-                p.allow_in_expr(|p| p.parse_expr())-            })+            .do_outside_of_statement_context(+                StatementContext::IgnoreElseClause,+                |p| with_grammar_context!(p, [+In, ?Yield, ?Await], |p| p.parse_expr()),+            )             .map_err(|err| {                 Error::new(                     err.span(),@@ -666,10 +686,9 @@ impl<I: Tokens> Parser<I> {                 // if !self.ctx().contains(Context::Strict) && self.input().is(Token::FUNCTION)                 // {     // TODO: report error?                 // }-                self.do_outside_of_context(-                    Context::IgnoreElseClause.union(Context::TopLevel),-                    Self::parse_stmt,-                )+                self.do_outside_of_statement_context(StatementContext::IgnoreElseClause, |p| {+                    p.do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt)+                })                 .map(Box::new)             })?         };@@ -677,7 +696,10 @@ impl<I: Tokens> Parser<I> {         // We parse `else` branch iteratively, to avoid stack overflow         // See https://github.com/swc-project/swc/pull/3961 -        let alt = if self.ctx().contains(Context::IgnoreElseClause) {+        let alt = if self+            .statement_ctx()+            .contains(StatementContext::IgnoreElseClause)+        {             None         } else {             let mut cur = None;@@ -689,17 +711,24 @@ impl<I: Tokens> Parser<I> {                  if !self.input().is(Token::If) {                     // As we eat `else` above, we need to parse statement once.-                    let last = self.do_outside_of_context(-                        Context::IgnoreElseClause.union(Context::TopLevel),-                        Self::parse_stmt,+                    let last = self.do_outside_of_statement_context(+                        StatementContext::IgnoreElseClause,+                        |p| {+                            p.do_outside_of_boundary_context(+                                BoundaryContext::TopLevel,+                                Self::parse_stmt,+                            )+                        },                     )?;                     break Some(last);                 }                  // We encountered `else if` -                let alt =-                    self.do_inside_of_context(Context::IgnoreElseClause, Self::parse_if_stmt)?;+                let alt = self.do_inside_of_statement_context(+                    StatementContext::IgnoreElseClause,+                    Self::parse_if_stmt,+                )?;                  match &mut cur {                     Some(cur) => {@@ -742,7 +771,7 @@ impl<I: Tokens> Parser<I> {             syntax_error!(self, SyntaxError::LineBreakInThrow);         } -        let arg = self.allow_in_expr(|p| p.parse_expr())?;+        let arg = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;         self.expect_general_semi()?;          let span = self.span(start);@@ -765,13 +794,11 @@ impl<I: Tokens> Parser<I> {         self.assert_and_bump(Token::With);          expect!(self, Token::LParen);-        let obj = self.allow_in_expr(|p| p.parse_expr())?;+        let obj = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;         expect!(self, Token::RParen);          let body = self-            .do_inside_of_context(Context::InFunction, |p| {-                p.do_outside_of_context(Context::TopLevel, Self::parse_stmt)-            })+            .do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt)             .map(Box::new)?;          let span = self.span(start);@@ -784,13 +811,13 @@ impl<I: Tokens> Parser<I> {         self.assert_and_bump(Token::While);          expect!(self, Token::LParen);-        let test = self.allow_in_expr(|p| p.parse_expr())?;+        let test = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;         expect!(self, Token::RParen);          let body = self-            .do_inside_of_context(-                Context::IsBreakAllowed.union(Context::IsContinueAllowed),-                |p| p.do_outside_of_context(Context::TopLevel, Self::parse_stmt),+            .do_inside_of_statement_context(+                StatementContext::IsBreakAllowed.union(StatementContext::IsContinueAllowed),+                |p| p.do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt),             )             .map(Box::new)?; @@ -806,7 +833,7 @@ impl<I: Tokens> Parser<I> {             let type_ann_start = self.cur_pos();              if self.syntax().typescript() && self.input_mut().eat(Token::Colon) {-                let ty = self.do_inside_of_context(Context::InType, Self::parse_ts_type)?;+                let ty = self.in_type(Self::parse_ts_type)?;                 // self.emit_err(ty.span(), SyntaxError::TS1196);                  match &mut pat {@@ -839,16 +866,16 @@ impl<I: Tokens> Parser<I> {         self.assert_and_bump(Token::Do);          let body = self-            .do_inside_of_context(-                Context::IsBreakAllowed.union(Context::IsContinueAllowed),-                |p| p.do_outside_of_context(Context::TopLevel, Self::parse_stmt),+            .do_inside_of_statement_context(+                StatementContext::IsBreakAllowed.union(StatementContext::IsContinueAllowed),+                |p| p.do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt),             )             .map(Box::new)?;          expect!(self, Token::While);         expect!(self, Token::LParen); -        let test = self.allow_in_expr(|p| p.parse_expr())?;+        let test = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;          expect!(self, Token::RParen); @@ -861,8 +888,8 @@ impl<I: Tokens> Parser<I> {     }      fn parse_labelled_stmt(&mut self, l: Ident) -> PResult<Stmt> {-        self.do_inside_of_context(Context::IsBreakAllowed, |p| {-            p.do_outside_of_context(Context::AllowUsingDecl, |p| {+        self.do_inside_of_statement_context(StatementContext::IsBreakAllowed, |p| {+            p.do_outside_of_statement_context(StatementContext::AllowUsingDecl, |p| {                 let start = l.span.lo();                  let mut errors = Vec::new();@@ -889,7 +916,7 @@ impl<I: Tokens> Parser<I> {                      f.into()                 } else {-                    p.do_outside_of_context(Context::TopLevel, Self::parse_stmt)?+                    p.do_outside_of_boundary_context(BoundaryContext::TopLevel, Self::parse_stmt)?                 });                  for err in errors {@@ -918,7 +945,7 @@ impl<I: Tokens> Parser<I> {          expect!(self, Token::LBrace); -        let stmts = self.do_outside_of_context(Context::TopLevel, |p| {+        let stmts = self.do_outside_of_boundary_context(BoundaryContext::TopLevel, |p| {             p.parse_stmt_block_body(allow_directives, Some(Token::RBrace))         })?; @@ -987,7 +1014,7 @@ impl<I: Tokens> Parser<I> {         self.assert_and_bump(Token::Switch);          expect!(self, Token::LParen);-        let discriminant = self.allow_in_expr(|p| p.parse_expr())?;+        let discriminant = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;         expect!(self, Token::RParen);          let mut cases = Vec::new();@@ -995,7 +1022,7 @@ impl<I: Tokens> Parser<I> {          expect!(self, Token::LBrace); -        self.do_inside_of_context(Context::IsBreakAllowed, |p| {+        self.do_inside_of_statement_context(StatementContext::IsBreakAllowed, |p| {             while {                 let cur = p.input().cur();                 cur == Token::Case || cur == Token::Default@@ -1005,7 +1032,7 @@ impl<I: Tokens> Parser<I> {                 let case_start = p.cur_pos();                 p.bump();                 let test = if is_case {-                    p.allow_in_expr(|p| p.parse_expr()).map(Some)?+                    with_grammar_context!(p, [+In, ?Yield, ?Await], |p| p.parse_expr()).map(Some)?                 } else {                     if let Some(previous) = span_of_previous_default {                         syntax_error!(p, SyntaxError::MultipleDefault { previous });@@ -1020,9 +1047,10 @@ impl<I: Tokens> Parser<I> {                     let cur = p.input().cur();                     !(cur == Token::Case || cur == Token::Default || cur == Token::RBrace)                 } {-                    cons.push(-                        p.do_outside_of_context(Context::TopLevel, Self::parse_stmt_list_item)?,-                    );+                    cons.push(p.do_outside_of_boundary_context(+                        BoundaryContext::TopLevel,+                        Self::parse_stmt_list_item,+                    )?);                 }                  cases.push(SwitchCase {@@ -1386,7 +1414,7 @@ impl<I: Tokens> Parser<I> {             }              if self.input_mut().eat(Token::LBracket) {-                let prop_expr = self.allow_in_expr(Self::parse_assignment_expr)?;+                let prop_expr = with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;                 expect!(self, Token::RBracket);                 expr = Box::new(Expr::Member(MemberExpr {                     span: self.span(start),@@ -1578,6 +1606,8 @@ impl<I: Tokens> Parser<I> {     }      fn flow_match_parse_pattern(&mut self) -> PResult<FlowMatchPattern> {+        // Flow allows one optional leading pipe, including in nested patterns.+        self.input_mut().eat(Token::Pipe);         let mut pat = self.flow_match_parse_primary_pattern()?;          while self.input_mut().eat(Token::Pipe) {@@ -1724,13 +1754,14 @@ impl<I: Tokens> Parser<I> {         while !self.input().is(Token::RBrace) {             let pattern = self.flow_match_parse_pattern()?;             let guard = if self.input_mut().eat(Token::If) {-                Some(self.allow_in_expr(|p| p.parse_expr())?)+                Some(with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?)             } else {                 None             };             self.flow_match_expect_case_separator()?; -            let body = self.allow_in_expr(Self::parse_assignment_expr)?;+            let body =+                with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;             let lowered = self.flow_match_lower_pattern(span, temp_expr.clone(), &pattern);             let mut test = lowered.predicate;             if let Some(guard) = guard {@@ -1778,7 +1809,7 @@ impl<I: Tokens> Parser<I> {         while !self.input().is(Token::RBrace) {             let pattern = self.flow_match_parse_pattern()?;             let guard = if self.input_mut().eat(Token::If) {-                Some(self.allow_in_expr(|p| p.parse_expr())?)+                Some(with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?)             } else {                 None             };@@ -1788,7 +1819,7 @@ impl<I: Tokens> Parser<I> {                 self.parse_block(false)?             } else {                 self.emit_err(self.input().cur_span(), SyntaxError::TS1003);-                let expr = self.allow_in_expr(Self::parse_assignment_expr)?;+                let expr = with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;                 BlockStmt {                     span,                     stmts: vec![Stmt::Expr(ExprStmt { span, expr })],@@ -1846,14 +1877,14 @@ impl<I: Tokens> Parser<I> {     /// Parse a statement and maybe a declaration.     pub fn parse_stmt_list_item(&mut self) -> PResult<Stmt> {         trace_cur!(self, parse_stmt_list_item);-        self.parse_stmt_like(true, handle_import_export)+        self.parse_stmt_like(StatementGrammar::StatementListItem, handle_import_export)     }      /// Parse a statement, declaration or module item.     #[inline(always)]-    pub(crate) fn parse_stmt_like<Type: From<Stmt>>(+    pub(super) fn parse_stmt_like<Type: From<Stmt>>(         &mut self,-        include_decl: bool,+        grammar: StatementGrammar,         handle_import_export: impl Fn(&mut Self, Vec<Decorator>) -> PResult<Type>,     ) -> PResult<Type> {         trace_cur!(self, parse_stmt_like);@@ -1872,9 +1903,9 @@ impl<I: Tokens> Parser<I> {             return handle_import_export(self, decorators);         } -        self.do_outside_of_context(Context::WillExpectColonForCond, |p| {-            p.do_inside_of_context(Context::AllowUsingDecl, |p| {-                p.parse_stmt_internal(start, include_decl, decorators)+        self.do_outside_of_type_context(TypeContext::WillExpectColonForCond, |p| {+            p.do_inside_of_statement_context(StatementContext::AllowUsingDecl, |p| {+                p.parse_stmt_internal(start, grammar, decorators)             })         })         .map(From::from)@@ -1884,11 +1915,13 @@ impl<I: Tokens> Parser<I> {     fn parse_stmt_internal(         &mut self,         start: BytePos,-        include_decl: bool,+        grammar: StatementGrammar,         decorators: Vec<Decorator>,     ) -> PResult<Stmt> {         trace_cur!(self, parse_stmt_internal); +        let include_decl = grammar.permits_declaration();+         let is_typescript = self.input().syntax().typescript();          if is_typescript@@ -1907,7 +1940,7 @@ impl<I: Tokens> Parser<I> {                 .map(Stmt::from);         } -        let top_level = self.ctx().contains(Context::TopLevel);+        let top_level = self.boundary_ctx().contains(BoundaryContext::TopLevel);          let cur = self.input().cur();         if self.is_flow_match_keyword() {@@ -1915,28 +1948,34 @@ impl<I: Tokens> Parser<I> {         }          if cur == Token::Await && (include_decl || top_level) {-            let handled_by_explicit_program =-                top_level && self.program_parse_mode == ProgramParseMode::None;+            let is_await_using = peek!(self).is_some_and(|peek| peek == Token::Using)+                && !self.input_mut().has_linebreak_between_cur_and_peeked();++            // In Script grammar, `await` can start an expression or a label.+            // Only `await using` needs a statement-level context error.+            let handled_by_explicit_program = top_level+                && self.program_parse_mode == ProgramParseMode::None+                && (self.ctx().contains(Context::Module)+                    || self.boundary_ctx().contains(BoundaryContext::CanBeModule)+                    || is_await_using);             if handled_by_explicit_program {                 self.mark_found_module_item();-                if !self.ctx().contains(Context::CanBeModule) {+                if !self.boundary_ctx().contains(BoundaryContext::CanBeModule) {                     self.emit_err(self.input().cur_span(), SyntaxError::TopLevelAwaitInScript);                 }             } -            let is_await_using = peek!(self).is_some_and(|peek| peek == Token::Using)-                && !self.input_mut().has_linebreak_between_cur_and_peeked();-             if is_await_using {                 let eaten_await = Some(self.input().cur_pos());+                let await_token = self.input().cur_span();+                self.check_current_token_escape()?;                 if self.can_classify_module() {                     self.mark_found_module_item();-                } else if !self-                    .ctx()-                    .intersects(Context::InAsync.union(Context::Module))+                } else if !self.includes_await_expr()+                    && !self.ctx().contains(Context::Module)                     && !handled_by_explicit_program                 {-                    let error = if self.ctx().contains(Context::InFunction) {+                    let error = if self.syntax_context == SyntaxContext::FunctionBody {                         SyntaxError::AwaitInFunction                     } else {                         SyntaxError::TopLevelAwaitInScript@@ -1946,11 +1985,17 @@ impl<I: Tokens> Parser<I> {                 self.assert_and_bump(Token::Await);                 let v = self.parse_using_decl(start, true)?;                 if let Some(v) = v {+                    // Static blocks use +Await, but Contains `await` is an early+                    // error. Check declarations here; the expression fallback+                    // below performs its own check in parse_await_expr.+                    if self.is_await_forbidden_in_initializer() {+                        self.emit_err(await_token, SyntaxError::ExpectedIdent);+                    }                     return Ok(v.into());                 }                  let expr = self.parse_await_expr(eaten_await)?;-                let expr = self.allow_in_expr(|p| p.parse_bin_op_recursively(expr, 0))?;+                let expr = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_bin_op_recursively(expr, 0))?;                 self.eat_general_semi();                  let span = self.span(start);@@ -1970,10 +2015,16 @@ impl<I: Tokens> Parser<I> {             if is_break {                 if label.is_some() && !self.state().labels.contains(&label.as_ref().unwrap().sym) {                     self.emit_err(span, SyntaxError::TS1116);-                } else if !self.ctx().contains(Context::IsBreakAllowed) {+                } else if !self+                    .statement_ctx()+                    .contains(StatementContext::IsBreakAllowed)+                {                     self.emit_err(span, SyntaxError::TS1105);                 }-            } else if !self.ctx().contains(Context::IsContinueAllowed) {+            } else if !self+                .statement_ctx()+                .contains(StatementContext::IsContinueAllowed)+            {                 self.emit_err(span, SyntaxError::TS1115);             } else if label.is_some() && !self.state().labels.contains(&label.as_ref().unwrap().sym)             {@@ -2049,8 +2100,8 @@ impl<I: Tokens> Parser<I> {         } else if cur == Token::Var || (cur == Token::Const && include_decl) {             let v = self.parse_var_stmt(false)?;             return Ok(v.into());-        } else if cur == Token::Let && include_decl {-            // 'let' can start an identifier reference.+        } else if cur == Token::Let && include_decl && !self.input().has_escaped_keyword() {+            // Only an unescaped 'let' can introduce a lexical declaration.             let is_keyword = match peek!(self) {                 Some(t) => t.follows_keyword_let(),                 _ => false,@@ -2085,14 +2136,16 @@ impl<I: Tokens> Parser<I> {             && is_typescript             && self.input().syntax().typescript_allows_enum()             && peek!(self).is_some_and(|peek| peek.is_word())-            && !self.input_mut().has_linebreak_between_cur_and_peeked()         {+            // A line break before an enum name does not terminate the declaration.             let start = self.input().cur_pos();             self.bump();             return Ok(self.parse_ts_enum_decl(start, false)?.into());         } else if cur == Token::LBrace {             return self-                .do_inside_of_context(Context::AllowUsingDecl, |p| p.parse_block(false))+                .do_inside_of_statement_context(StatementContext::AllowUsingDecl, |p| {+                    p.parse_block(false)+                })                 .map(Stmt::Block);         } else if cur == Token::Semi {             self.bump();@@ -2116,7 +2169,7 @@ impl<I: Tokens> Parser<I> {         // next token is a colon and the expression was a simple         // Identifier node, we switch to interpreting it as a label.         let expr_token = self.input().cur();-        let expr = self.allow_in_expr(|p| p.parse_expr())?;+        let expr = with_grammar_context!(self, [+In, ?Yield, ?Await], |p| p.parse_expr())?;          let expr = match *expr {             Expr::Ident(ident) => {@@ -2192,10 +2245,17 @@ impl<I: Tokens> Parser<I> {         allow_directives: bool,         end: Option<Token>,     ) -> PResult<Vec<Stmt>> {-        self.parse_block_body(allow_directives, end, handle_import_export)+        let stmts = self.parse_block_body(allow_directives, end, handle_import_export)?;+        if self.input().cur() != Token::Eof && end.is_some() {+            self.bump();+        }+        Ok(stmts)     } -    pub(crate) fn parse_block_body<Type: From<Stmt>>(+    /// Parses a body without consuming its closing delimiter. The caller can+    /// restore the enclosing lexical context before reading the following+    /// token.+    pub(super) fn parse_block_body<Type: super::context::BlockBodyItem>(         &mut self,         allow_directives: bool,         end: Option<Token>,@@ -2231,12 +2291,12 @@ impl<I: Tokens> Parser<I> {                         break;                     } -                    let stmt = p.parse_stmt_like(true, &handle_import_export)?;+                    let stmt = p.parse_stmt_like(Type::GRAMMAR, &handle_import_export)?;                     stmts.push(stmt);                 }             } else {                 while p.input().cur() != Token::Eof {-                    let stmt = p.parse_stmt_like(true, &handle_import_export)?;+                    let stmt = p.parse_stmt_like(Type::GRAMMAR, &handle_import_export)?;                     stmts.push(stmt);                 }             }@@ -2250,10 +2310,6 @@ impl<I: Tokens> Parser<I> {             parse_stmts(self, &mut stmts)?;         }; -        if self.input().cur() != Token::Eof && end.is_some() {-            self.bump();-        }-         Ok(stmts)     } }

deps/swc/crates/swc_ecma_parser/src/parser/tsrx.rs

@@ -19,8 +19,8 @@ use swc_atoms::{atom, Atom}; use swc_common::{BytePos, EqIgnoreSpan, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; -use super::{input::Tokens, stmt::TempForHead, Parser};-use crate::{error::SyntaxError, lexer::Token, Context, PResult};+use super::{input::Tokens, stmt::TempForHead, BoundaryContext, Parser, StatementContext};+use crate::{error::SyntaxError, lexer::Token, PResult};  const REACT_SOURCE: &str = "react"; const ERROR_BOUNDARY_SOURCE: &str = "@tsrx/react/error-boundary";@@ -398,8 +398,8 @@ impl<I: Tokens> Parser<I> {     }      fn parse_tsrx_branch(&mut self) -> PResult<CodeBlock> {-        self.do_outside_of_context(-            Context::IsBreakAllowed.union(Context::IsContinueAllowed),+        self.do_outside_of_statement_context(+            StatementContext::IsBreakAllowed.union(StatementContext::IsContinueAllowed),             |parser| parser.parse_tsrx_code_block(false),         )     }@@ -407,17 +407,19 @@ impl<I: Tokens> Parser<I> {     fn parse_tsrx_for(&mut self, start: BytePos) -> PResult<ForDirective> {         self.assert_and_bump(Token::For);         let await_start = self.cur_pos();-        let is_await = self.input_mut().eat(Token::Await);+        let is_await = self.input().is(Token::Await);+        if is_await {+            self.check_current_token_escape()?;+            self.bump();+        }         let await_span = is_await.then(|| self.span(await_start));         expect!(self, Token::LParen); -        let head = self.do_inside_of_context(Context::ForLoopInit, |p| {-            if is_await {-                p.do_inside_of_context(Context::ForAwaitLoopInit, Self::parse_for_head)-            } else {-                p.do_outside_of_context(Context::ForAwaitLoopInit, Self::parse_for_head)-            }-        })?;+        let head = if is_await {+            self.parse_for_head_with_await(true)?+        } else {+            self.parse_for_head()?+        };          let mut index = None;         let mut key = None;@@ -430,19 +432,23 @@ impl<I: Tokens> Parser<I> {                         unexpected!(self, "`key` after `index` in a TSRX @for header")                     }                     self.bump();-                    key = Some(self.allow_in_expr(Self::parse_assignment_expr)?);+                    key = Some(+                        with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?,+                    );                 }             } else if self.is_contextual_word("key") {                 self.bump();-                key = Some(self.allow_in_expr(Self::parse_assignment_expr)?);+                key = Some(+                    with_grammar_context!(self, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?,+                );             } else {                 unexpected!(self, "`index` or `key` in a TSRX @for header")             }         }         expect!(self, Token::RParen); -        let body = self.do_inside_of_context(-            Context::IsBreakAllowed.union(Context::IsContinueAllowed),+        let body = self.do_inside_of_statement_context(+            StatementContext::IsBreakAllowed.union(StatementContext::IsContinueAllowed),             |p| p.parse_tsrx_code_block(false),         )?;         let empty = self.parse_tsrx_named_block("empty")?;@@ -494,7 +500,7 @@ impl<I: Tokens> Parser<I> {         self.assert_and_bump(Token::Switch);         let discriminant = self.parse_tsrx_condition()?;         expect!(self, Token::LBrace);-        let cases = self.do_inside_of_context(Context::IsBreakAllowed, |p| {+        let cases = self.do_inside_of_statement_context(StatementContext::IsBreakAllowed, |p| {             let mut cases = Vec::new();             let mut previous_default = None;             while !p.input().is(Token::RBrace) {@@ -1102,11 +1108,11 @@ impl<I: Tokens> Parser<I> {     }      fn suspension_context(&self) -> SuspensionContext {-        let context = self.ctx();-        match (-            context.contains(Context::InAsync),-            context.contains(Context::InGenerator),-        ) {+        if self.boundary_ctx().contains(BoundaryContext::InStaticBlock) {+            return SuspensionContext::Synchronous;+        }++        match (self.includes_await_expr(), self.includes_yield_expr()) {             (false, false) => SuspensionContext::Synchronous,             (true, false) => SuspensionContext::Async,             (false, true) => SuspensionContext::Generator,

deps/swc/crates/swc_ecma_parser/src/parser/typescript.rs

@@ -6,8 +6,11 @@ use swc_common::{BytePos, Span, Spanned}; use swc_ecma_ast::*;  use crate::{-    error::SyntaxError, input::Tokens, lexer::Token, parser::util::IsSimpleParameterList, Context,-    PResult, Parser,+    error::SyntaxError,+    input::Tokens,+    lexer::Token,+    parser::{util::IsSimpleParameterList, BoundaryContext, FunctionKind, TypeContext},+    Context, PResult, Parser, };  #[derive(Debug, Clone, Copy, PartialEq, Eq)]@@ -19,6 +22,13 @@ enum ParsingContext {     TypeParametersOrArguments, } +/// Expression type arguments cannot split a comparison or shift operator.+#[derive(Clone, Copy, PartialEq, Eq)]+enum AngleClose {+    Exact,+    Peel,+}+ #[derive(Clone, Copy, PartialEq, Eq)] enum UnionOrIntersection {     Union,@@ -325,6 +335,8 @@ impl<I: Tokens> Parser<I> {             }         }; +        // A renamed component prop can place `?` after its local binding.+        let optional = optional || (has_as && self.input_mut().eat(Token::QuestionMark));         if optional {             self.flow_mark_pat_optional(&mut value);         }@@ -448,8 +460,17 @@ impl<I: Tokens> Parser<I> {         }         let return_type = self.parse_flow_component_renders_ann()?; -        let body = self.parse_fn_block_body(false, false, false, true)?;-        if self.syntax().flow() && body.is_none() && !self.ctx().contains(Context::InDeclare) {+        let body = self.parse_fn_block_body(+            FunctionKind::Function {+                is_async: false,+                is_generator: false,+            },+            true,+        )?;+        if self.syntax().flow()+            && body.is_none()+            && !self.type_ctx().contains(TypeContext::InDeclare)+        {             self.emit_err(self.input().cur_span(), SyntaxError::TS1005);         } @@ -698,33 +719,6 @@ impl<I: Tokens> Parser<I> {         Ok(buf)     } -    /// `tsTryParse`-    pub(super) fn try_parse_ts_bool<F>(&mut self, op: F) -> PResult<bool>-    where-        F: FnOnce(&mut Self) -> PResult<Option<bool>>,-    {-        if !self.input().syntax().typescript() {-            return Ok(false);-        }--        let prev_ignore_error = self.input().get_ctx().contains(Context::IgnoreError);-        let checkpoint = self.checkpoint_save();-        self.set_ctx(self.ctx() | Context::IgnoreError);-        let res = op(self);-        match res {-            Ok(Some(res)) if res => {-                let mut ctx = self.ctx();-                ctx.set(Context::IgnoreError, prev_ignore_error);-                self.input_mut().set_ctx(ctx);-                Ok(res)-            }-            _ => {-                self.checkpoint_load(checkpoint);-                Ok(false)-            }-        }-    }-     /// `tsParseDelimitedList`     fn parse_ts_delimited_list_inner<T, F>(         &mut self,@@ -771,18 +765,6 @@ impl<I: Tokens> Parser<I> {         Ok(buf)     } -    /// In no lexer context-    pub(crate) fn ts_in_no_context<T, F>(&mut self, op: F) -> PResult<T>-    where-        F: FnOnce(&mut Self) -> PResult<T>,-    {-        debug_assert!(self.input().syntax().typescript());-        trace_cur!(self, ts_in_no_context__before);-        let res = op(self);-        trace_cur!(self, ts_in_no_context__after);-        res-    }-     /// `tsIsListTerminator`     fn is_ts_list_terminator(&mut self, kind: ParsingContext) -> bool {         debug_assert!(self.input().syntax().typescript());@@ -793,22 +775,21 @@ impl<I: Tokens> Parser<I> {                 matches!(cur, Token::LBrace | Token::Implements | Token::Extends)             }             ParsingContext::TupleElementTypes => cur == Token::RBracket,-            ParsingContext::TypeParametersOrArguments => cur == Token::Gt,+            ParsingContext::TypeParametersOrArguments => {+                cur == Token::Gt || cur.should_rescan_into_gt_in_jsx()+            }         }     }      /// `tsNextTokenCanFollowModifier`     pub(super) fn ts_next_token_can_follow_modifier(&mut self) -> bool {         debug_assert!(self.input().syntax().typescript());-        // Note: TypeScript's implementation is much more complicated because-        // more things are considered modifiers there.-        // This implementation only handles modifiers not handled by @babel/parser-        // itself. And "static". TODO: Would be nice to avoid lookahead. Want a-        // hasLineBreakUpNext() method...-        self.bump();--        let cur = self.input().cur();-        !self.input().had_line_break_before_cur()+        // Decide without consuming the candidate: a failed modifier probe must+        // not leave deferred keyword-escape errors in the lexer.+        let Some(cur) = peek!(self) else {+            return false;+        };+        !self.input_mut().has_linebreak_between_cur_and_peeked()             && matches!(                 cur,                 Token::LBracket@@ -844,7 +825,7 @@ impl<I: Tokens> Parser<I> {                 trace_cur!(self, try_parse_ts__success_value);                 let mut ctx = self.ctx();                 ctx.set(Context::IgnoreError, prev_ignore_error);-                self.input_mut().set_ctx(ctx);+                self.set_ctx(ctx);                 Some(res)             }             Ok(None) => {@@ -1013,13 +994,17 @@ impl<I: Tokens> Parser<I> {         }          if let Some(pos) = pos {+            if self.input().has_escaped_keyword() {+                return Ok(None);+            }             if stop_on_start_of_class_static_blocks                 && self.input().is(Token::Static)                 && peek!(self).is_some_and(|peek| peek == Token::LBrace)             {                 return Ok(None);             }-            if self.try_parse_ts_bool(|p| Ok(Some(p.ts_next_token_can_follow_modifier())))? {+            if self.ts_next_token_can_follow_modifier() {+                self.bump();                 return Ok(Some(allowed_modifiers[pos]));             }         }@@ -1048,7 +1033,7 @@ impl<I: Tokens> Parser<I> {         if bracket {             expect!(self, Token::RBracket);         } else {-            expect!(self, Token::Gt);+            self.expect_ts_type_gt()?;         }         Ok(result)     }@@ -1068,7 +1053,7 @@ impl<I: Tokens> Parser<I> {         trace_cur!(self, parse_ts_entity_name);         let start = self.input().cur_pos();         let init_token = self.input().cur();-        let init = self.parse_ident_name()?;+        let init = self.parse_ident_name_with_escape_check()?;         if init_token == Token::Void {             let dot_start = self.input().cur_pos();             let dot_span = self.span(dot_start);@@ -1109,45 +1094,62 @@ impl<I: Tokens> Parser<I> {         ret     } -    /// `tsParseTypeArguments`+    /// Type arguments may start with adjacent opening angles after+    /// tokenization.+    fn is_ts_type_args_start(&self) -> bool {+        matches!(self.input().cur(), Token::Lt | Token::LShift)+    }++    /// Parses a complete type argument list, including its closing `>`.+    /// The following token is read in the caller's lexical context.     pub(crate) fn parse_ts_type_args(&mut self) -> PResult<Box<TsTypeParamInstantiation>> {+        self.parse_ts_type_args_with_close(AngleClose::Peel)+    }++    pub(crate) fn parse_ts_type_args_in_expr(&mut self) -> PResult<Box<TsTypeParamInstantiation>> {+        self.parse_ts_type_args_with_close(AngleClose::Exact)+    }++    fn parse_ts_type_args_with_close(+        &mut self,+        close: AngleClose,+    ) -> PResult<Box<TsTypeParamInstantiation>> {         trace_cur!(self, parse_ts_type_args);         debug_assert!(self.input().syntax().typescript());          let start = self.input().cur_pos();         let params = self.in_type(|p| {-            // Temporarily remove a JSX parsing context, which makes us scan different-            // tokens.-            p.ts_in_no_context(|p| {-                if p.input().is(Token::LShift) {-                    p.input_mut().cut_lshift();-                } else {-                    expect!(p, Token::Lt);-                }-                if p.syntax().flow() {-                    p.parse_ts_delimited_list(ParsingContext::TypeParametersOrArguments, |p| {-                        trace_cur!(p, parse_ts_type_args__arg);+            if p.input().is(Token::LShift) {+                p.input_mut().cut_lshift();+            } else {+                expect!(p, Token::Lt);+            }+            if p.syntax().flow() {+                p.parse_ts_delimited_list(ParsingContext::TypeParametersOrArguments, |p| {+                    trace_cur!(p, parse_ts_type_args__arg); -                        p.do_outside_of_context(-                            Context::DisallowFlowAnonFnType,-                            Self::parse_ts_type,-                        )-                    })-                } else {-                    p.parse_ts_delimited_list(ParsingContext::TypeParametersOrArguments, |p| {-                        trace_cur!(p, parse_ts_type_args__arg);+                    p.do_outside_of_type_context(+                        TypeContext::DisallowFlowAnonFnType,+                        Self::parse_ts_type,+                    )+                })+            } else {+                p.parse_ts_delimited_list(ParsingContext::TypeParametersOrArguments, |p| {+                    trace_cur!(p, parse_ts_type_args__arg); -                        p.parse_ts_type()-                    })-                }-            })+                    p.parse_ts_type()+                })+            }         })?;         // This reads the next token after the `>` too, so do this in the enclosing         // context. But be sure not to parse a regex in the jsx expression         // `<C<number> />`, so set exprAllowed = false         self.input_mut().set_expr_allowed(false);-        self.expect_without_advance(Token::Gt)?;-        let span = Span::new_with_checked(start, self.input().cur_span().hi);+        if close == AngleClose::Exact {+            self.expect_without_advance(Token::Gt)?;+        }+        self.expect_ts_type_gt()?;+        let span = self.span(start);          // Report grammar error for empty type argument list like `I<>`.         // Flow allows this form in several positions.@@ -1158,6 +1160,15 @@ impl<I: Tokens> Parser<I> {         Ok(Box::new(TsTypeParamInstantiation { span, params }))     } +    /// Consumes one closing angle, leaving any operator suffix for the caller.+    pub(super) fn expect_ts_type_gt(&mut self) -> PResult<()> {+        if self.input_mut().eat_type_gt() {+            Ok(())+        } else {+            self.expect(Token::Gt)+        }+    }+     /// `tsParseTypeReference`     fn parse_ts_type_ref(&mut self) -> PResult<TsTypeRef> {         trace_cur!(self, parse_ts_type_ref);@@ -1177,14 +1188,9 @@ impl<I: Tokens> Parser<I> {         }         trace_cur!(self, parse_ts_type_ref__type_args);         let type_params = if (self.syntax().flow() || !self.input().had_line_break_before_cur())-            && (self.input().is(Token::Lt) || self.input().is(Token::LShift))+            && self.is_ts_type_args_start()         {-            let ret = self.do_outside_of_context(-                Context::ShouldNotLexLtOrGtAsType,-                Self::parse_ts_type_args,-            )?;-            self.assert_and_bump(Token::Gt);-            Some(ret)+            Some(self.parse_ts_type_args()?)         } else {             None         };@@ -1299,7 +1305,7 @@ impl<I: Tokens> Parser<I> {         debug_assert!(self.input().syntax().typescript());          let start = self.input().cur_pos();-        let name = self.parse_ident_name()?;+        let name = self.parse_ident_name_with_escape_check()?;         let constraint = Some(self.expect_then_parse_ts_type(Token::In, "in")?);          Ok(TsTypeParam {@@ -1455,7 +1461,7 @@ impl<I: Tokens> Parser<I> {             };         } -        let name = self.in_type(Self::parse_ident_name)?;+        let name = self.in_type(Self::parse_ts_binding_ident_name)?;         if self.syntax().flow() {             self.emit_flow_reserved_type_name_error(name.span, &name.sym);         }@@ -1489,44 +1495,42 @@ impl<I: Tokens> Parser<I> {         permit_const: bool,     ) -> PResult<Box<TsTypeParamDecl>> {         self.in_type(|p| {-            p.ts_in_no_context(|p| {-                let start = p.input().cur_pos();-                let cur = p.input().cur();-                if cur != Token::Lt && cur != Token::JSXTagStart {-                    unexpected!(p, "< (jsx tag start)")-                }-                p.bump();+            let start = p.input().cur_pos();+            let cur = p.input().cur();+            if cur != Token::Lt && cur != Token::JSXTagStart {+                unexpected!(p, "< (jsx tag start)")+            }+            p.bump(); -                let params = p.parse_ts_bracketed_list(-                    ParsingContext::TypeParametersOrArguments,-                    |p| p.parse_ts_type_param(permit_in_out, permit_const), // bracket-                    false,-                    // skip_first_token-                    true,-                )?;+            let params = p.parse_ts_bracketed_list(+                ParsingContext::TypeParametersOrArguments,+                |p| p.parse_ts_type_param(permit_in_out, permit_const), // bracket+                false,+                // skip_first_token+                true,+            )?; -                if p.syntax().flow() && params.is_empty() {-                    p.emit_err(p.span(start), SyntaxError::TS1005);-                }+            if p.syntax().flow() && params.is_empty() {+                p.emit_err(p.span(start), SyntaxError::TS1005);+            } -                if p.syntax().flow() {-                    let mut saw_default = false;-                    for param in &params {-                        if param.default.is_some() {-                            saw_default = true;-                        } else if saw_default {-                            // Flow requires all subsequent parameters to provide defaults once-                            // one default is introduced.-                            p.emit_err(param.span, SyntaxError::TS1005);-                        }+            if p.syntax().flow() {+                let mut saw_default = false;+                for param in &params {+                    if param.default.is_some() {+                        saw_default = true;+                    } else if saw_default {+                        // Flow requires all subsequent parameters to provide defaults once+                        // one default is introduced.+                        p.emit_err(param.span, SyntaxError::TS1005);                     }                 }+            } -                Ok(Box::new(TsTypeParamDecl {-                    span: p.span(start),-                    params,-                }))-            })+            Ok(Box::new(TsTypeParamDecl {+                span: p.span(start),+                params,+            }))         })     } @@ -1578,7 +1582,7 @@ impl<I: Tokens> Parser<I> {                  if p.input_mut().eat(Token::LParen) {                     if !p.input().is(Token::RParen) {-                        p.allow_in_expr(Self::parse_assignment_expr)?;+                        with_grammar_context!(p, [+In, ?Yield, ?Await], Self::parse_assignment_expr)?;                     }                     expect!(p, Token::RParen);                 }@@ -1590,16 +1594,18 @@ impl<I: Tokens> Parser<I> {             }              let type_pred_start = p.input().cur_pos();-            let has_type_pred_asserts = p.input().cur() == Token::Asserts && {-                let ctx = p.ctx();-                peek!(p).is_some_and(|peek| {-                    if peek.is_word() {-                        !peek.is_reserved(ctx)-                    } else {-                        false-                    }-                })-            };+            // In TypeScript, `asserts` and its parameter must be on the same line.+            let has_type_pred_asserts = p.input().cur() == Token::Asserts+                && {+                    peek!(p).is_some_and(|peek| {+                        if peek.is_word() {+                            !p.token_is_reserved(peek)+                        } else {+                            false+                        }+                    })+                }+                && (p.syntax().flow() || !p.input_mut().has_linebreak_between_cur_and_peeked());             let has_flow_implies = p.syntax().flow()                 && p.input().cur().is_word()                 && p.input().cur().take_word(&p.input) == atom!("implies")@@ -1636,7 +1642,7 @@ impl<I: Tokens> Parser<I> {             let param_name = if p.input().is(Token::This) {                 TsThisTypeOrIdent::TsThisType(p.parse_ts_this_type_node()?)             } else {-                let ident = p.parse_ident_name()?;+                let ident = p.parse_ident_name_with_escape_check()?;                 if has_flow_implies && ident.sym == *"implies" {                     p.emit_err(ident.span, SyntaxError::TS1003);                 }@@ -1702,8 +1708,7 @@ impl<I: Tokens> Parser<I> {         debug_assert!(self.input().syntax().typescript());          self.try_parse_ts(|p| {-            let type_args = p.parse_ts_type_args()?;-            p.assert_and_bump(Token::Gt);+            let type_args = p.parse_ts_type_args_in_expr()?;             let cur = p.input().cur();             if matches!(                 cur,@@ -1768,19 +1773,10 @@ impl<I: Tokens> Parser<I> {     pub(super) fn next_then_parse_ts_type(&mut self) -> PResult<Box<TsType>> {         debug_assert!(self.input().syntax().typescript()); -        let result = self.in_type(|p| {+        self.in_type(|p| {             p.bump();             p.parse_ts_type()-        });--        if !self.ctx().contains(Context::InType) && {-            let cur = self.input().cur();-            cur == Token::Lt || cur == Token::Gt-        } {-            self.input_mut().merge_lt_gt();-        }--        result+        })     }      fn parse_flow_enum_explicit_kind(&mut self) -> PResult<Option<FlowEnumKind>> {@@ -2139,7 +2135,7 @@ impl<I: Tokens> Parser<I> {             return self.parse_flow_enum_decl(start, is_const);         } -        let id = self.parse_ident_name()?;+        let id = self.parse_ts_binding_ident_name()?;         expect!(self, Token::LBrace);         let members =             self.parse_ts_delimited_list(ParsingContext::EnumMembers, Self::parse_ts_enum_member)?;@@ -2270,7 +2266,7 @@ impl<I: Tokens> Parser<I> {         // Note: TS uses parseLeftHandSideExpressionOrHigher,         // then has grammar errors later if it's not an EntityName. -        let ident = self.parse_ident_name()?.into();+        let ident = self.parse_ident_name_with_escape_check()?.into();         let expr = self.parse_subscripts(Callee::Expr(ident), true, true)?;         if !matches!(             &*expr,@@ -2287,9 +2283,7 @@ impl<I: Tokens> Parser<I> {             }),             _ => {                 let type_args = if self.input().is(Token::Lt) {-                    let ret = self.parse_ts_type_args()?;-                    self.assert_and_bump(Token::Gt);-                    Some(ret)+                    Some(self.parse_ts_type_args()?)                 } else {                     None                 };@@ -2329,8 +2323,10 @@ impl<I: Tokens> Parser<I> {     fn is_ts_unambiguously_start_of_fn_type(&mut self) -> PResult<bool> {         debug_assert!(self.input().syntax().typescript()); -        let disallow_flow_anon_fn_type =-            self.is_flow_syntax() && self.ctx().contains(Context::DisallowFlowAnonFnType);+        let disallow_flow_anon_fn_type = self.is_flow_syntax()+            && self+                .type_ctx()+                .contains(TypeContext::DisallowFlowAnonFnType);          self.assert_and_bump(Token::LParen);         let starts_with_parenthesized_object_or_array =@@ -2470,7 +2466,9 @@ impl<I: Tokens> Parser<I> {         expect!(self, Token::LBracket);          let ident_start = self.cur_pos();-        let mut id = self.parse_ident_name().map(BindingIdent::from)?;+        let mut id = self+            .parse_ident_name_with_escape_check()+            .map(BindingIdent::from)?;         let type_ann_start = self.cur_pos();          if self.input_mut().eat(Token::Comma) {@@ -2509,7 +2507,7 @@ impl<I: Tokens> Parser<I> {         is_static: bool,     ) -> PResult<Option<TsIndexSignature>> {         if !self.input().syntax().flow()-            || !self.ctx().contains(Context::InType)+            || !self.type_ctx().contains(TypeContext::InType)             || self.input().cur() != Token::LBracket         {             return Ok(None);@@ -2609,6 +2607,15 @@ impl<I: Tokens> Parser<I> {     ///     /// Eats ')` at the end but does not eat `(` at start.     fn parse_ts_binding_list_for_signature(&mut self) -> PResult<Vec<TsFnParam>> {+        self.do_inside_of_boundary_context(BoundaryContext::InParameters, |p| {+            p.with_syntax_context(+                super::SyntaxContext::Parameters,+                Self::parse_ts_binding_list_for_signature_inner,+            )+        })+    }++    fn parse_ts_binding_list_for_signature_inner(&mut self) -> PResult<Vec<TsFnParam>> {         if !cfg!(feature = "typescript") {             return Ok(Default::default());         }@@ -2640,94 +2647,29 @@ impl<I: Tokens> Parser<I> {             Ok(item)         } -        if self.input().syntax().flow() && self.ctx().contains(Context::InType) {-            let mut list = Vec::with_capacity(4);-            let mut rest_span = Span::default();--            while !self.input().is(Token::RParen) {-                if !rest_span.is_dummy() {-                    self.emit_err(rest_span, SyntaxError::TS1014);-                }--                if let Some(param) =-                    self.try_parse_ts(|p| p.try_parse_flow_anon_signature_param(list.len()))-                {-                    if matches!(param, TsFnParam::Rest(..)) {-                        rest_span = param.span();-                    }-                    list.push(param);-                } else {-                    let pat_start = self.cur_pos();-                    let pat = if self.input_mut().eat(Token::DotDotDot) {-                        let dot3_token = self.span(pat_start);--                        let mut pat = self.parse_binding_pat_or_ident(false)?;--                        if self.input_mut().eat(Token::Eq) {-                            let right = self.parse_assignment_expr()?;-                            self.emit_err(pat.span(), SyntaxError::TS1048);-                            pat = AssignPat {-                                span: self.span(pat_start),-                                left: Box::new(pat),-                                right,-                            }-                            .into();-                        }--                        let type_ann = if self.input().syntax().typescript()-                            && self.input().is(Token::Colon)-                        {-                            let cur_pos = self.cur_pos();-                            Some(self.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?)-                        } else {-                            None-                        };--                        let pat: Pat = RestPat {-                            span: self.span(pat_start),-                            dot3_token,-                            arg: Box::new(pat),-                            type_ann,-                        }-                        .into();--                        if self.syntax().typescript() && self.input_mut().eat(Token::QuestionMark) {-                            self.emit_err(self.input().prev_span(), SyntaxError::TS1047);-                        }--                        rest_span = pat.span();-                        pat-                    } else {-                        self.parse_formal_param_pat()?-                    };--                    let is_rest = matches!(pat, Pat::Rest(..));-                    let item = pat_to_ts_fn_param(self, pat)?;-                    list.push(item);--                    if is_rest && !self.input().is(Token::RParen) {-                        rest_span = list.last().expect("rest param").span();-                    }-                }--                if !self.input().is(Token::RParen) {-                    expect!(self, Token::Comma);-                    if !rest_span.is_dummy() && self.input().is(Token::RParen) {-                        self.emit_err(self.input().prev_span(), SyntaxError::CommaAfterRestElement);+        if self.input().syntax().flow() && self.type_ctx().contains(TypeContext::InType) {+            let list = self.parse_parameter_list(|p, index| {+                if p.flow_starts_like_anon_signature_param() {+                    if let Some(param) =+                        p.try_parse_ts(|p| p.try_parse_flow_anon_signature_param(index))+                    {+                        return Ok(param);                     }                 }-            }+                let pat_start = p.cur_pos();+                let pat = if p.input_mut().eat(Token::DotDotDot) {+                    p.parse_rest_parameter(pat_start)?+                } else {+                    p.parse_formal_param_pat()?+                };+                pat_to_ts_fn_param(p, pat)+            })?;              expect!(self, Token::RParen);             return Ok(list);         } -        let params = self.parse_formal_params()?;-        let mut list = Vec::with_capacity(4);--        for param in params {-            list.push(pat_to_ts_fn_param(self, param.pat)?);-        }+        let list = self.parse_formal_params_inner(|p, param| pat_to_ts_fn_param(p, param.pat))?;         expect!(self, Token::RParen);         Ok(list)     }@@ -2818,7 +2760,7 @@ impl<I: Tokens> Parser<I> {                 None             }; -            let mut ident = p.parse_ident_name().map(Ident::from)?;+            let mut ident = p.parse_ident_name_with_escape_check().map(Ident::from)?;             if p.input_mut().eat(Token::QuestionMark) {                 ident.optional = true;                 ident.span = ident.span.with_hi(p.input().prev_span().hi);@@ -2881,6 +2823,14 @@ impl<I: Tokens> Parser<I> {          let label = self.try_parse_ts_tuple_element_name(); +        // Validate the confirmed label outside speculative parsing.+        if let Some(Pat::Rest(rest)) = &label {+            let is_optional = matches!(rest.arg.as_ref(), Pat::Ident(ident) if ident.id.optional);+            if is_optional && !self.input().syntax().flow() {+                syntax_error!(self, rest.span, SyntaxError::TsOptionalRestElement);+            }+        }+         if self.input().syntax().flow() {             if variance_span.is_some() && label.is_none() {                 self.emit_err(variance_span.unwrap(), SyntaxError::TS1003);@@ -2943,7 +2893,7 @@ impl<I: Tokens> Parser<I> {          let start = self.cur_pos();         let elems = if self.input().syntax().flow() {-            self.do_outside_of_context(Context::DisallowFlowAnonFnType, |p| {+            self.do_outside_of_type_context(TypeContext::DisallowFlowAnonFnType, |p| {                 p.parse_ts_bracketed_list(                     ParsingContext::TupleElementTypes,                     Self::parse_ts_tuple_element_type,@@ -2960,22 +2910,23 @@ impl<I: Tokens> Parser<I> {             )?         }; -        // Validate the elementTypes to ensure:-        //   No mandatory elements may follow optional elements-        //   If there's a rest element, it must be at the end of the tuple-+        // No required elements may follow optional elements. Named elements store+        // their optional/rest markers on the label instead of the type.         let mut seen_optional_element = false;          for elem in elems.iter() {-            match *elem.ty {-                TsType::TsRestType(..) => {}-                TsType::TsOptionalType(..) => {-                    seen_optional_element = true;-                }-                _ if seen_optional_element => {-                    syntax_error!(self, self.span(start), SyntaxError::TsRequiredAfterOptional)-                }-                _ => {}+            if matches!(&elem.label, Some(Pat::Rest(..)))+                || matches!(*elem.ty, TsType::TsRestType(..))+            {+                continue;+            }++            let is_optional = matches!(&elem.label, Some(Pat::Ident(ident)) if ident.id.optional)+                || matches!(*elem.ty, TsType::TsOptionalType(..));+            if is_optional {+                seen_optional_element = true;+            } else if seen_optional_element {+                syntax_error!(self, self.span(start), SyntaxError::TsRequiredAfterOptional)             }         } @@ -3054,7 +3005,10 @@ impl<I: Tokens> Parser<I> {         let start = self.cur_pos();         expect!(self, Token::LParen);         let type_ann = if self.input().syntax().flow() {-            self.do_outside_of_context(Context::DisallowFlowAnonFnType, Self::parse_ts_type)?+            self.do_outside_of_type_context(+                TypeContext::DisallowFlowAnonFnType,+                Self::parse_ts_type,+            )?         } else {             self.parse_ts_type()?         };@@ -3072,7 +3026,7 @@ impl<I: Tokens> Parser<I> {     ) -> PResult<Box<TsTypeAliasDecl>> {         debug_assert!(self.input().syntax().typescript()); -        let id = self.parse_ident_name()?;+        let id = self.parse_ts_binding_ident_name()?;         if self.syntax().flow() {             self.emit_flow_reserved_type_name_error(id.span, &id.sym);         }@@ -3110,7 +3064,7 @@ impl<I: Tokens> Parser<I> {         debug_assert!(self.input().syntax().flow());          expect!(self, Token::Type);-        let id = self.parse_ident_name()?;+        let id = self.parse_ts_binding_ident_name()?;         if self.syntax().flow() {             self.emit_flow_reserved_type_name_error(id.span, &id.sym);         }@@ -3122,7 +3076,7 @@ impl<I: Tokens> Parser<I> {         }          let type_ann = if self.input_mut().eat(Token::Eq) {-            if self.ctx().contains(Context::InDeclare) {+            if self.type_ctx().contains(TypeContext::InDeclare) {                 self.emit_err(self.input().prev_span(), SyntaxError::TS1003);             }             self.in_type(Self::parse_ts_type)?@@ -3291,8 +3245,14 @@ impl<I: Tokens> Parser<I> {         cur.is_word() && peek!(self).is_some_and(|peek| peek == Token::Lt || peek == Token::LShift)     } +    /// Reject named parameters before saving a speculative parser checkpoint.+    /// Rest parameters still need speculation after consuming the spread token.+    fn flow_starts_like_anon_signature_param(&mut self) -> bool {+        self.input().is(Token::DotDotDot) || self.flow_starts_like_anon_signature_param_type()+    }+     fn try_parse_flow_anon_signature_param(&mut self, index: usize) -> PResult<Option<TsFnParam>> {-        if !self.input().syntax().flow() || !self.ctx().contains(Context::InType) {+        if !self.input().syntax().flow() || !self.type_ctx().contains(TypeContext::InType) {             return Ok(None);         } @@ -3330,7 +3290,11 @@ impl<I: Tokens> Parser<I> {         &mut self,         index: usize,     ) -> PResult<Option<Pat>> {-        if !self.input().syntax().flow() || !self.ctx().contains(Context::InDeclare) {+        if !self.input().syntax().flow() || !self.type_ctx().contains(TypeContext::InDeclare) {+            return Ok(None);+        }++        if !self.flow_starts_like_anon_signature_param() {             return Ok(None);         } @@ -3343,7 +3307,9 @@ impl<I: Tokens> Parser<I> {      fn try_parse_flow_anon_fn_type(&mut self) -> PResult<Option<TsFnType>> {         if !self.input().syntax().flow()-            || self.ctx().contains(Context::DisallowFlowAnonFnType)+            || self+                .type_ctx()+                .contains(TypeContext::DisallowFlowAnonFnType)             || !self.input().is(Token::LParen)         {             return Ok(None);@@ -3467,7 +3433,9 @@ impl<I: Tokens> Parser<I> {                     let ty = self.parse_ts_array_type_or_higher(readonly)?;                      if self.is_flow_syntax()-                        && !self.ctx().contains(Context::DisallowFlowAnonFnType)+                        && !self+                            .type_ctx()+                            .contains(TypeContext::DisallowFlowAnonFnType)                         && !self.input().had_line_break_before_cur()                         && self.input().is(Token::Arrow)                         && !matches!(&*ty, TsType::TsThisType(..))@@ -3518,11 +3486,11 @@ impl<I: Tokens> Parser<I> {          let start = self.cur_pos();         expect!(self, Token::Infer);-        let type_param_name = self.parse_ident_name()?;+        let type_param_name = self.parse_ident_name_with_escape_check()?;         let constraint = self.try_parse_ts(|p| {             expect!(p, Token::Extends);             let constraint = p.parse_ts_non_conditional_type();-            if p.ctx().contains(Context::DisallowConditionalTypes)+            if p.type_ctx().contains(TypeContext::DisallowConditionalTypes)                 || !p.input().is(Token::QuestionMark)             {                 constraint.map(Some)@@ -3645,12 +3613,12 @@ impl<I: Tokens> Parser<I> {          debug_assert!(self.input().syntax().typescript()); -        // Need to set `state.inType` so that we don't parse JSX in a type context.-        debug_assert!(self.ctx().contains(Context::InType));+        // Callers explicitly enter type grammar before parsing a type.+        debug_assert!(self.type_ctx().contains(TypeContext::InType));          let start = self.cur_pos(); -        self.do_outside_of_context(Context::DisallowConditionalTypes, |p| {+        self.do_outside_of_type_context(TypeContext::DisallowConditionalTypes, |p| {             let ty = p.parse_ts_non_conditional_type()?;             if p.input().syntax().flow()                 && !p.input().had_line_break_before_cur()@@ -3677,8 +3645,8 @@ impl<I: Tokens> Parser<I> {             }              let check_type = ty;-            let extends_type = p.do_inside_of_context(-                Context::DisallowConditionalTypes,+            let extends_type = p.do_inside_of_type_context(+                TypeContext::DisallowConditionalTypes,                 Self::parse_ts_non_conditional_type,             )?; @@ -3730,27 +3698,25 @@ impl<I: Tokens> Parser<I> {             expect!(self, Token::RBracket);             (true, key)         } else {-            self.do_inside_of_context(Context::InPropertyName, |p| {-                // We check if it's valid for it to be a private name when we push it.-                let cur = p.input().cur();+            // We check if it's valid for it to be a private name when we push it.+            let cur = self.input().cur(); -                let key = if cur == Token::Num || cur == Token::Str {-                    p.parse_new_expr()-                } else if cur == Token::Error {-                    let err = p.input_mut().expect_error_token_and_bump();-                    return Err(err);-                } else {-                    p.parse_maybe_private_name().map(|e| match e {-                        Either::Left(e) => {-                            p.emit_err(e.span(), SyntaxError::PrivateNameInInterface);+            let key = if cur == Token::Num || cur == Token::Str {+                self.parse_new_expr()+            } else if cur == Token::Error {+                let err = self.input_mut().expect_error_token_and_bump();+                return Err(err);+            } else {+                self.parse_maybe_private_name().map(|e| match e {+                    Either::Left(e) => {+                        self.emit_err(e.span(), SyntaxError::PrivateNameInInterface); -                            e.into()-                        }-                        Either::Right(e) => e.into(),-                    })-                };-                key.map(|key| (false, key))-            })?+                        e.into()+                    }+                    Either::Right(e) => e.into(),+                })+            };+            key.map(|key| (false, key))?         };          Ok((computed, key))@@ -4032,7 +3998,7 @@ impl<I: Tokens> Parser<I> {             }         };         let members = if self.input().syntax().flow() {-            self.do_outside_of_context(Context::DisallowFlowAnonFnType, parse_members)?+            self.do_outside_of_type_context(TypeContext::DisallowFlowAnonFnType, parse_members)?         } else {             parse_members(self)?         };@@ -4085,7 +4051,7 @@ impl<I: Tokens> Parser<I> {     ) -> PResult<Box<TsInterfaceDecl>> {         debug_assert!(self.input().syntax().typescript()); -        let id = self.parse_ident_name()?;+        let id = self.parse_ts_binding_ident_name()?;         match &*id.sym {             "string" | "null" | "number" | "object" | "any" | "unknown" | "boolean" | "bigint"             | "symbol" | "void" | "never" | "intrinsic" => {@@ -4157,7 +4123,7 @@ impl<I: Tokens> Parser<I> {         // plugin is enabled, but need `tsInType` to satisfy the assertion in         // `tsParseType`.         let type_ann = self.in_type(Self::parse_ts_type)?;-        expect!(self, Token::Gt);+        self.expect_ts_type_gt()?;         let expr = self.parse_unary_expr()?;         Ok(TsTypeAssertion {             span: self.span(start),@@ -4210,13 +4176,8 @@ impl<I: Tokens> Parser<I> {             None         }; -        let type_args = if self.input().is(Token::Lt) {-            let ret = self.do_outside_of_context(-                Context::ShouldNotLexLtOrGtAsType,-                Self::parse_ts_type_args,-            )?;-            self.assert_and_bump(Token::Gt);-            Some(ret)+        let type_args = if self.is_ts_type_args_start() {+            Some(self.parse_ts_type_args()?)         } else {             None         };@@ -4270,13 +4231,9 @@ impl<I: Tokens> Parser<I> {             self.parse_ts_entity_name(true).map(From::from)?         }; -        let type_args = if !self.input().had_line_break_before_cur() && self.input().is(Token::Lt) {-            let ret = self.do_outside_of_context(-                Context::ShouldNotLexLtOrGtAsType,-                Self::parse_ts_type_args,-            )?;-            self.assert_and_bump(Token::Gt);-            Some(ret)+        let type_args = if !self.input().had_line_break_before_cur() && self.is_ts_type_args_start()+        {+            Some(self.parse_ts_type_args()?)         } else {             None         };@@ -4296,11 +4253,24 @@ impl<I: Tokens> Parser<I> {          let start = self.cur_pos();         expect!(self, Token::LBrace);-        let body = self.do_inside_of_context(Context::TsModuleBlock, |p| {-            p.do_outside_of_context(Context::TopLevel, |p| {-                p.parse_module_item_block_body(false, Some(Token::RBrace))+        let outer_ctx = self.ctx();+        let body = self.do_inside_of_type_context(TypeContext::TsModuleBlock, |p| {+            p.do_outside_of_boundary_context(BoundaryContext::TopLevel, |p| {+                with_grammar_context!(p, [?Yield, ?Await, ~Return], |p| {+                    p.with_syntax_context(super::SyntaxContext::Nested, |p| {+                        p.parse_module_item_block_body(false, Some(Token::RBrace))+                    })+                })             })-        })?;+        });+        // Imports may enable Module/Strict inside this declaration container.+        // Restore before consuming `}`: advancing also lexes the outer token.+        self.set_ctx(outer_ctx);+        let body = body?;+        // The body parser already reports an unterminated container at EOF.+        if self.input().cur() != Token::Eof {+            expect!(self, Token::RBrace);+        }          Ok(TsModuleBlock {             span: self.span(start),@@ -4313,13 +4283,18 @@ impl<I: Tokens> Parser<I> {         &mut self,         start: BytePos,         namespace: bool,+        nested: bool,     ) -> PResult<Box<TsModuleDecl>> {         debug_assert!(self.input().syntax().typescript()); -        let id = self.parse_ident_name()?;+        let id = if nested {+            self.parse_ident_name()?+        } else {+            self.parse_ts_binding_ident_name()?+        };         let body: TsNamespaceBody = if self.input_mut().eat(Token::Dot) {             let inner_start = self.cur_pos();-            let inner = self.parse_ts_module_or_ns_decl(inner_start, namespace)?;+            let inner = self.parse_ts_module_or_ns_decl(inner_start, namespace, true)?;             let inner = TsNamespaceDecl {                 span: inner.span,                 id: match inner.id {@@ -4444,6 +4419,8 @@ impl<I: Tokens> Parser<I> {         {             if self.input().is(Token::Asserts)                 && peek!(self).is_some_and(|peek| peek == Token::This)+                && (self.syntax().flow()+                    || !self.input_mut().has_linebreak_between_cur_and_peeked())             {                 self.bump();                 let this_keyword = self.parse_ts_this_type_node()?;@@ -4675,6 +4652,12 @@ impl<I: Tokens> Parser<I> {          match &*expr.sym {             "declare" => {+                // A line break after `declare` terminates the expression statement, so the+                // following declaration is not ambient.+                if !self.syntax().flow() && self.input().had_line_break_before_cur() {+                    return Ok(None);+                }+                 let decl = self.try_parse_ts_declare(start, decorators)?;                 if let Some(decl) = decl {                     Ok(Some(make_decl_declare(decl)))@@ -4725,15 +4708,15 @@ impl<I: Tokens> Parser<I> {          if !self.syntax().flow()             && self-                .ctx()-                .contains(Context::InDeclare | Context::TsModuleBlock)+                .type_ctx()+                .contains(TypeContext::InDeclare | TypeContext::TsModuleBlock)         {             let span_of_declare = self.span(start);             self.emit_err(span_of_declare, SyntaxError::TS1038);         }          let declare_start = start;-        self.do_inside_of_context(Context::InDeclare, |p| {+        self.do_inside_of_type_context(TypeContext::InDeclare, |p| {             if p.input().is(Token::Function) {                 return p                     .parse_fn_decl(decorators)@@ -5088,7 +5071,7 @@ impl<I: Tokens> Parser<I> {                 }                  if self.input().syntax().flow()-                    && self.ctx().contains(Context::InDeclare)+                    && self.type_ctx().contains(TypeContext::InDeclare)                     && self.input_mut().eat(Token::Dot)                 {                     let is_exports = self.input().cur().is_word()@@ -5125,7 +5108,7 @@ impl<I: Tokens> Parser<I> {                     return Err(self.eof_error());                 } else if next || self.is_ident_ref() {                     return self-                        .parse_ts_module_or_ns_decl(start, false)+                        .parse_ts_module_or_ns_decl(start, false, false)                         .map(From::from)                         .map(Some);                 }@@ -5137,7 +5120,7 @@ impl<I: Tokens> Parser<I> {                         self.bump();                     }                     return self-                        .parse_ts_module_or_ns_decl(start, true)+                        .parse_ts_module_or_ns_decl(start, true, false)                         .map(From::from)                         .map(Some);                 }@@ -5162,7 +5145,7 @@ impl<I: Tokens> Parser<I> {                     if next {                         self.bump();                     }-                    let declare = self.ctx().contains(Context::InDeclare);+                    let declare = self.type_ctx().contains(TypeContext::InDeclare);                     return self                         .parse_flow_component_decl(start, decorators, declare)                         .map(Some);@@ -5174,7 +5157,7 @@ impl<I: Tokens> Parser<I> {                     if next {                         self.bump();                     }-                    let declare = self.ctx().contains(Context::InDeclare);+                    let declare = self.type_ctx().contains(TypeContext::InDeclare);                     return self                         .parse_flow_hook_decl(start, decorators, declare)                         .map(Some);@@ -5233,16 +5216,12 @@ impl<I: Tokens> Parser<I> {                  // Don't use overloaded parseFunctionParams which would look for "<" again.                 expect!(p, Token::LParen);-                let params: Vec<Pat> = p-                    .parse_formal_params()?-                    .into_iter()-                    .map(|p| p.pat)-                    .collect();+                let params = p.parse_formal_param_patterns()?;                 expect!(p, Token::RParen);                 let return_type = if p.input().syntax().flow() {                     // In arrow return type context, `T => expr` belongs to the                     // outer arrow unless the function type is parenthesized.-                    p.do_inside_of_context(Context::DisallowFlowAnonFnType, |p| {+                    p.do_inside_of_type_context(TypeContext::DisallowFlowAnonFnType, |p| {                         p.try_parse_ts_type_or_type_predicate_ann()                     })?                 } else {@@ -5261,28 +5240,25 @@ impl<I: Tokens> Parser<I> {             None => return Ok(None),         }; -        self.do_inside_of_context(Context::InAsync, |p| {-            p.do_outside_of_context(Context::InGenerator, |p| {-                let is_generator = false;-                let is_async = true;-                p.record_await_in_arrow_params(&params);-                let body = p.parse_fn_block_or_expr_body(-                    true,-                    false,-                    true,-                    params.is_simple_parameter_list(),-                )?;-                Ok(Some(ArrowExpr {-                    span: p.span(start),-                    body,-                    is_async,-                    is_generator,-                    type_params: Some(type_params),-                    params,-                    return_type,-                    ..Default::default()-                }))-            })+        // AsyncConciseBody[?In] derives ExpressionBody[?In, +Await].+        with_grammar_context!(self, [?In, ~Yield, +Await], |p| {+            let is_generator = false;+            let is_async = true;+            p.record_await_in_arrow_params(&params);+            let body = p.parse_fn_block_or_expr_body(+                FunctionKind::Arrow { is_async: true },+                params.is_simple_parameter_list(),+            )?;+            Ok(Some(ArrowExpr {+                span: p.span(start),+                body,+                is_async,+                is_generator,+                type_params: Some(type_params),+                params,+                return_type,+                ..Default::default()+            }))         })     } }

deps/swc/crates/swc_ecma_parser/src/parser/typescript_stubs.rs

@@ -48,6 +48,14 @@ impl<I: Tokens> Parser<I> {         unreachable!("parse_ts_type_args should not be called without typescript feature")     } +    pub(crate) fn parse_ts_type_args_in_expr(&mut self) -> PResult<Box<TsTypeParamInstantiation>> {+        unreachable!("parse_ts_type_args_in_expr should not be called without typescript feature")+    }++    pub(super) fn expect_ts_type_gt(&mut self) -> PResult<()> {+        unreachable!("expect_ts_type_gt should not be called without typescript feature")+    }+     pub(crate) fn parse_ts_type_ann(         &mut self,         _eat_colon: bool,

deps/swc/crates/swc_ecma_parser/src/parser/util.rs

@@ -1,6 +1,53 @@ use swc_common::Span; use swc_ecma_ast::*; +/// Checks the syntactic forms allowed for an unannotated ambient readonly+/// initializer. Whether a member reference actually names an enum member is a+/// type-checking concern.+pub(super) fn is_ts_ambient_initializer(expr: &Expr) -> bool {+    match expr {+        Expr::Lit(Lit::Bool(_) | Lit::BigInt(_)) => true,+        Expr::Unary(UnaryExpr {+            op: UnaryOp::Minus,+            arg,+            ..+        }) if matches!(&**arg, Expr::Lit(Lit::BigInt(_))) => true,+        Expr::Member(member) => {+            let mut object = &*member.obj;+            while let Expr::Member(MemberExpr {+                obj,+                prop: MemberProp::Ident(_),+                ..+            }) = object+            {+                object = obj;+            }+            if !matches!(object, Expr::Ident(_)) {+                return false;+            }+            match &member.prop {+                MemberProp::Ident(_) => true,+                MemberProp::Computed(key) => is_ts_ambient_string_or_number(&key.expr),+                _ => false,+            }+        }+        _ => is_ts_ambient_string_or_number(expr),+    }+}++fn is_ts_ambient_string_or_number(expr: &Expr) -> bool {+    match expr {+        Expr::Lit(Lit::Str(_) | Lit::Num(_)) => true,+        Expr::Tpl(template) => template.exprs.is_empty(),+        Expr::Unary(UnaryExpr {+            op: UnaryOp::Minus,+            arg,+            ..+        }) => matches!(&**arg, Expr::Lit(Lit::Num(_))),+        _ => false,+    }+}+ pub trait IsSimpleParameterList {     fn is_simple_parameter_list(&self) -> bool; }

deps/swc/crates/swc_ecma_preset_env/Cargo.toml

@@ -13,7 +13,7 @@ include = [ license = { workspace = true } name = "swc_ecma_preset_env" repository = { workspace = true }-version = "63.0.0"+version = "66.0.0"  [lib] bench = false@@ -29,7 +29,7 @@ once_cell  = { workspace = true } serde      = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true } -preset_env_base = { version = "9.0.0", path = "../preset_env_base" }+preset_env_base = { version = "10.0.0", path = "../preset_env_base" }  foldhash        = { workspace = true } precomputed-map = { workspace = true }@@ -38,13 +38,13 @@ rustc-hash = { workspace = true } string_enum = { version = "1.0.2", path = "../string_enum" } swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transformer = { version = "21.0.1", path = "../swc_ecma_transformer" }-swc_ecma_transforms = { version = "62.0.0", path = "../swc_ecma_transforms", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transformer = { version = "22.0.0", path = "../swc_ecma_transformer" }+swc_ecma_transforms = { version = "64.0.0", path = "../swc_ecma_transforms", features = [   "compat",   "proposal", ] }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  [build-dependencies]@@ -59,9 +59,13 @@ serde_json      = { workspace = true } codspeed-criterion-compat = { workspace = true } pretty_assertions         = { workspace = true } -swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_parser  = { version = "45.1.1", path = "../swc_ecma_parser" }-testing          = { version = "27.0.0", path = "../testing" }+swc_ecma_codegen            = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_base    = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [+  "inline-helpers",+] }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }+testing                    = { version = "27.0.0", path = "../testing" }  [[bench]] harness = false

deps/swc/crates/swc_ecma_quote/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_quote" repository    = { workspace = true }-version       = "45.0.0"+version       = "46.0.0"  [lib] bench = false@@ -16,7 +16,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies] swc_common            = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast          = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_quote_macros = { version = "45.0.1", path = "../swc_ecma_quote_macros" }+swc_ecma_ast          = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_quote_macros = { version = "46.0.0", path = "../swc_ecma_quote_macros" }  [dev-dependencies]

deps/swc/crates/swc_ecma_quote_macros/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_quote_macros" repository    = { workspace = true }-version       = "45.0.1"+version       = "46.0.0"  [lib] bench      = false@@ -24,8 +24,8 @@ syn         = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] } swc_macros_common = { version = "1.0.1", path = "../swc_macros_common" }

deps/swc/crates/swc_ecma_quote_macros/src/ret_type.rs

@@ -3,7 +3,7 @@ use std::any::type_name; use anyhow::{anyhow, bail, Context, Error}; use swc_common::{sync::Lrc, FileName, SourceMap}; use swc_ecma_ast::{AssignTarget, EsVersion};-use swc_ecma_parser::{lexer::Lexer, Context as ParserContext, PResult, Parser, StringInput};+use swc_ecma_parser::{lexer::Lexer, PResult, Parser, StringInput}; use syn::{GenericArgument, PathArguments, Type};  use crate::{ast::ToCode, ctxt::Ctx};@@ -74,7 +74,7 @@ where     // Quoted nodes use direct parser entry points rather than parsing a Program.     // Preserve their module-capable grammar so top-level `await` is quoted as an     // `AwaitExpr`, while explicit Script parsing remains unchanged.-    parser.set_ctx(parser.ctx().union(ParserContext::CanBeModule));+    parser.allow_module_syntax();     op(&mut parser)         .map_err(|err| anyhow!("{err:?}"))         .with_context(|| format!("failed to parse input as `{}`", type_name::<T>()))

deps/swc/crates/swc_ecma_react_compiler/Cargo.toml

@@ -5,7 +5,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_react_compiler" repository    = { workspace = true }-version       = "25.0.0"+version       = "26.0.0"   [package.metadata.docs.rs]@@ -22,16 +22,17 @@ serde              = { workspace = true, features = ["derive"] } serde_json         = { workspace = true } swc_atoms          = { version = "10.0.0", path = "../swc_atoms" } swc_common         = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast       = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen   = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_parser    = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_ast       = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen   = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_parser    = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }+swc_ecma_utils     = { version = "35.0.3", path = "../swc_ecma_utils", features = ["stacker"] } swc_ecma_visit     = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] swc_common      = { version = "26.0.0", path = "../swc_common" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] } testing         = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_react_compiler/src/convert_ast.rs

@@ -23,7 +23,7 @@ use swc_common::{ }; use swc_ecma_ast as swc; -use crate::preserved_ast::PreservedAst;+use crate::{preserved_ast::PreservedAst, stack::with_expression_stack};  pub struct ConvertResult {     pub file: File,@@ -615,6 +615,10 @@ impl<'a> ConvertCtx<'a> {     // ===== Expressions =====      fn convert_expr(&self, expr: &swc::Expr) -> Expression {+        with_expression_stack(|| self.convert_expr_inner(expr))+    }++    fn convert_expr_inner(&self, expr: &swc::Expr) -> Expression {         match expr {             swc::Expr::Lit(lit) => match lit {                 swc::Lit::Str(s) => Expression::StringLiteral(StringLiteral {@@ -763,7 +767,7 @@ impl<'a> ConvertCtx<'a> {                 let (property, computed) = self.convert_member_prop(&m.prop);                 Expression::OptionalMemberExpression(OptionalMemberExpression {                     base: self.make_base_node(chain.span),-                    object: Box::new(self.convert_expr_in_chain(&m.obj)),+                    object: Box::new(self.convert_expr(&m.obj)),                     property: Box::new(property),                     computed,                     optional: chain.optional,@@ -776,7 +780,7 @@ impl<'a> ConvertCtx<'a> {                  Expression::OptionalCallExpression(OptionalCallExpression {                     base: self.make_base_node(chain.span),-                    callee: Box::new(self.convert_expr_in_chain(&call.callee)),+                    callee: Box::new(self.convert_expr(&call.callee)),                     arguments: call                         .args                         .iter()@@ -793,19 +797,6 @@ impl<'a> ConvertCtx<'a> {         }     } -    fn convert_expr_in_chain(&self, expr: &swc::Expr) -> Expression {-        if Self::expr_contains_optional(expr) {-            if let swc::Expr::OptChain(chain) = expr {-                return self.convert_opt_chain_expr(chain);-            }-        }-        self.convert_expr(expr)-    }--    fn expr_contains_optional(expr: &swc::Expr) -> bool {-        matches!(expr, swc::Expr::OptChain(_))-    }-     fn convert_member_expr(&self, member: &swc::MemberExpr) -> MemberExpression {         let (property, computed) = self.convert_member_prop(&member.prop);         MemberExpression {

deps/swc/crates/swc_ecma_react_compiler/src/convert_ast_reverse.rs

@@ -23,7 +23,7 @@ use swc_atoms::Atom; use swc_common::{BytePos, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast as swc; -use crate::preserved_ast::PreservedAst;+use crate::{preserved_ast::PreservedAst, stack::with_expression_stack};  /// Convert with source text and preserved SWC nodes from the forward pass. pub fn convert_program_to_swc(file: &File, preserved_ast: PreservedAst) -> swc::Program {@@ -604,6 +604,10 @@ impl ReverseCtx {     // ===== Expressions =====      fn convert_expression(&self, expr: &Expression) -> swc::Expr {+        with_expression_stack(|| self.convert_expression_inner(expr))+    }++    fn convert_expression_inner(&self, expr: &Expression) -> swc::Expr {         match expr {             Expression::Identifier(id) => swc::Expr::Ident(self.convert_identifier(id)),             Expression::StringLiteral(lit) => {

deps/swc/crates/swc_ecma_react_compiler/src/fast_check.rs

@@ -5,6 +5,8 @@ use swc_ecma_ast::{ }; use swc_ecma_visit::{Visit, VisitWith}; +use crate::stack::with_expression_stack;+ /// Conservatively determines whether infer or annotation mode could compile /// anything. ///@@ -93,6 +95,13 @@ fn module_has_opt_in_directive(module: &Module) -> bool { }  impl Visit for Finder {+    fn visit_expr(&mut self, node: &Expr) {+        if self.found {+            return;+        }+        with_expression_stack(|| node.visit_children_with(self));+    }+     fn visit_arrow_expr(&mut self, node: &ArrowExpr) {         if let ArrowFunctionBody::FunctionBody(body) = &*node.body {             if has_opt_in_directive(&body.stmts) {

deps/swc/crates/swc_ecma_react_compiler/src/lib.rs

@@ -12,6 +12,7 @@ pub mod fast_check; mod prefilter; mod preserved_ast; mod source_type;+mod stack;  #[cfg(test)] mod tests;@@ -76,6 +77,9 @@ pub struct LintResult {  /// Transform a pre-parsed program. `program` is `None` when nothing was /// compiled.+///+/// Infer and annotation modes skip programs without JSX, plausible hook calls,+/// or opt-in directives before building scopes or converting the AST. #[must_use] pub fn transform(     program: &Program,@@ -84,6 +88,16 @@ pub fn transform(     comments: Option<&SingleThreadedComments>,     options: PluginOptions, ) -> TransformResult {+    if matches!(options.compilation_mode.as_str(), "infer" | "annotation")+        && !fast_check::is_required(program)+    {+        return TransformResult {+            program: None,+            diagnostics: vec![],+            events: vec![],+        };+    }+     if has_resource_management_declarations(program) {         return TransformResult {             program: None,@@ -92,6 +106,18 @@ pub fn transform(         };     } +    stack::with_compiler_stack(|| {+        transform_with_stack(program, source_type, source_text, comments, options)+    })+}++fn transform_with_stack(+    program: &Program,+    source_type: SourceType,+    source_text: &str,+    comments: Option<&SingleThreadedComments>,+    options: PluginOptions,+) -> TransformResult {     let source_type = source_type.with_module(matches!(program, Program::Module(_)));     let scope_info = SemanticBuilder::with_source_type(source_type).build(program);     let ConvertResult {

deps/swc/crates/swc_ecma_react_compiler/src/prefilter.rs

@@ -6,6 +6,8 @@ use swc_ecma_ast as swc; use swc_ecma_visit::{Visit, VisitWith}; +use crate::stack::with_expression_stack;+ /// Whether the program contains `using` or `await using` declarations. pub fn has_resource_management_declarations(program: &swc::Program) -> bool {     let mut visitor = ResourceManagementVisitor { found: false };@@ -18,6 +20,13 @@ struct ResourceManagementVisitor { }  impl Visit for ResourceManagementVisitor {+    fn visit_expr(&mut self, expr: &swc::Expr) {+        if self.found {+            return;+        }+        with_expression_stack(|| expr.visit_children_with(self));+    }+     fn visit_var_decl(&mut self, decl: &swc::VarDecl) {         if self.found {             return;

deps/swc/crates/swc_ecma_react_compiler/src/stack.rs

new file mode 100644@@ -0,0 +1,23 @@+use swc_ecma_utils::stack_size::maybe_grow;++/// Protect recursive expression conversion and the scans preceding compilation.+///+/// These frames are larger than the usual SWC visitors, so keep 256 KiB free+/// before entering another expression and grow in 1 MiB segments.+#[inline]+pub(crate) fn with_expression_stack<R>(callback: impl FnOnce() -> R) -> R {+    maybe_grow(256 * 1024, 1024 * 1024, callback)+}++/// Provide the stack budget used by the upstream React Compiler's napi bridge.+///+/// Its AST visitors, HIR lowering, and AST destruction recurse without stack+/// growth checks. Keep the entire compilation, including temporary AST drops,+/// on this stack instead of spawning another thread for each file. This is a+/// finite budget, not support for arbitrarily deep input. The shared SWC helper+/// runs callbacks directly on wasm32, ARM, and Miri, where growth is+/// unsupported.+pub(crate) fn with_compiler_stack<R>(callback: impl FnOnce() -> R) -> R {+    const STACK_SIZE: usize = 64 * 1024 * 1024;+    maybe_grow(STACK_SIZE, STACK_SIZE, callback)+}

deps/swc/crates/swc_ecma_testing/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_testing" repository    = { workspace = true }-version       = "27.0.0"+version       = "27.0.1"  [lib] bench = false

deps/swc/crates/swc_ecma_testing/src/lib.rs

@@ -1,7 +1,6 @@ use std::{     env, fs,     path::{Path, PathBuf},-    process::Command,     sync::atomic::{AtomicU64, Ordering}, }; @@ -13,6 +12,10 @@ use tracing::debug;  static CACHE_WRITE_ID: AtomicU64 = AtomicU64::new(0); +mod node;+#[cfg(test)]+mod tests;+ #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct JsExecOptions {     /// Cache the result of the execution.@@ -70,21 +73,7 @@ pub fn exec_node_js(js_code: &str, opts: JsExecOptions) -> Result<String> {     #[cfg(debug_assertions)]     debug!("Executing nodejs:\n{}", js_code); -    let mut c = Command::new("node");--    if opts.module {-        c.arg("--input-type=module");-    } else {-        c.arg("--input-type=commonjs");-    }--    c.arg("-e").arg(js_code);--    for arg in opts.args {-        c.arg(arg);-    }--    let output = c.output().context("failed to execute output of minifier")?;+    let output = node::exec(js_code, &opts).context("failed to execute output of minifier")?;      if !output.status.success() {         bail!(

deps/swc/crates/swc_ecma_testing/src/node.rs

new file mode 100644@@ -0,0 +1,67 @@+use std::{+    io,+    process::{Command, Output},+};+#[cfg(any(windows, test))]+use std::{io::Write, process::Stdio};++use crate::JsExecOptions;++fn command(opts: &JsExecOptions) -> Command {+    let mut command = Command::new("node");+    command.arg(if opts.module {+        "--input-type=module"+    } else {+        "--input-type=commonjs"+    });+    command+}++pub(super) fn exec(js_code: &str, opts: &JsExecOptions) -> io::Result<Output> {+    let output = command(opts)+        .arg("-e")+        .arg(js_code)+        .args(&opts.args)+        .output();++    #[cfg(windows)]+    if let Err(error) = &output {+        // Windows reports an oversized command line as+        // ERROR_FILENAME_EXCED_RANGE (206). ES5 output with inline source maps+        // can exceed that limit even when the input fixture is small.+        if error.raw_os_error() == Some(206) {+            return exec_with_stdin(js_code, opts);+        }+    }++    output+}++/// Execute oversized Windows scripts without putting their source in argv.+#[cfg(any(windows, test))]+pub(super) fn exec_with_stdin(js_code: &str, opts: &JsExecOptions) -> io::Result<Output> {+    let mut child = command(opts)+        // Normalize argv before module dependencies execute. Using a preload+        // also keeps the original source intact, including inline source maps.+        // Node adds a stdin marker that -e does not include in user arguments.+        .arg("--import=data:text/javascript,process.argv.splice(1,1)")+        .arg("-")+        .args(&opts.args)+        .stdin(Stdio::piped())+        .stdout(Stdio::piped())+        .stderr(Stdio::piped())+        .spawn()?;++    let written = {+        let mut stdin = child.stdin.take().unwrap();+        stdin.write_all(js_code.as_bytes())+    };++    // Close stdin before waiting so Node can evaluate the complete script.+    // Always reap the child, including when writing its input fails.+    let output = child.wait_with_output()?;+    if output.status.success() {+        written?;+    }+    Ok(output)+}

deps/swc/crates/swc_ecma_transformer/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transformer" repository    = { workspace = true }-version       = "21.0.1"+version       = "22.0.0"  [features] default = []@@ -20,12 +20,12 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] } rustc-hash = { workspace = true } swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_compat_regexp = { version = "9.0.0", path = "../swc_ecma_compat_regexp" } swc_ecma_hooks = { version = "1.0.0", path = "../swc_ecma_hooks" } swc_ecma_regexp = { version = "0.15.0", path = "../swc_ecma_regexp" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit", features = [     "path", ] }

deps/swc/crates/swc_ecma_transformer/src/common/var_declarations.rs

@@ -1,5 +1,5 @@-//! Utility transform to add `var` or `let` declarations to top of statement-//! blocks.+//! Utility transform to add `var` or `let` declarations after directives in+//! statement blocks. //! //! `VarDeclarationsStore` contains a stack of `Declarators`, each comprising //! 2 x `Vec<VarDeclarator>` (1 for `var`s, 1 for `let`s).@@ -7,7 +7,7 @@ //! //! `VarDeclarations` transform pushes an empty entry onto this stack when //! entering a statement block, and when exiting the block, writes `var` / `let`-//! statements to top of block.+//! statements after the block's directive prologue. //! //! Other transforms can add declarators to the store by calling methods of //! `VarDeclarationsStore`:@@ -17,17 +17,18 @@ //! ctx.var_declarations.insert_let(ident, Some(init), ctx); //! ``` -use std::{mem, ptr};+use std::ptr;  use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_hooks::VisitMutHook;+use swc_ecma_utils::StmtLikeInjector;  use crate::TraverseCtx;  /// Transform that maintains the stack of `Vec<VarDeclarator>`s, and adds a-/// `var` statement to top of a statement block if another transform has-/// requested that.+/// `var` statement after directives in a statement block if another transform+/// has requested that. /// /// Must run after all other transforms. #[derive(Debug, Default)]@@ -163,11 +164,7 @@ impl VarDeclarations {             return;         } -        let original = mem::take(&mut block.stmts);-        let mut stmts = Vec::with_capacity(declarations.len() + original.len());-        stmts.extend(declarations);-        stmts.extend(original);-        block.stmts = stmts;+        block.stmts.prepend_stmts(declarations);     } } @@ -270,45 +267,18 @@ impl VarDeclarationsStore {      fn insert_into_stmts(&mut self, stmts: &mut Vec<Stmt>) {         if let Some((var_statement, let_statement)) = self.get_var_statement() {-            let mut new_stmts = Vec::with_capacity(stmts.len() + 2);--            match (var_statement, let_statement) {-                (Some(var_statement), Some(let_statement)) => {-                    // Insert `var` and `let` statements-                    new_stmts.push(var_statement);-                    new_stmts.push(let_statement);-                }-                (Some(statement), None) | (None, Some(statement)) => {-                    // Insert `var` or `let` statement-                    new_stmts.push(statement);-                }-                (None, None) => return,-            }--            new_stmts.append(stmts);-            *stmts = new_stmts;+            stmts.prepend_stmts(var_statement.into_iter().chain(let_statement));         }     }      fn insert_into_module_items(&mut self, items: &mut Vec<ModuleItem>) {         if let Some((var_statement, let_statement)) = self.get_var_statement() {-            let mut new_items = Vec::with_capacity(items.len() + 2);--            match (var_statement, let_statement) {-                (Some(var_statement), Some(let_statement)) => {-                    // Insert `var` and `let` statements-                    new_items.push(ModuleItem::Stmt(var_statement));-                    new_items.push(ModuleItem::Stmt(let_statement));-                }-                (Some(statement), None) | (None, Some(statement)) => {-                    // Insert `var` or `let` statement-                    new_items.push(ModuleItem::Stmt(statement));-                }-                (None, None) => return,-            }--            new_items.append(items);-            *items = new_items;+            items.prepend_stmts(+                var_statement+                    .into_iter()+                    .chain(let_statement)+                    .map(ModuleItem::Stmt),+            );         }     } 

deps/swc/crates/swc_ecma_transformer/src/es2017/async_to_generator.rs

@@ -12,6 +12,10 @@ use swc_ecma_visit::VisitMutWith;  use crate::TraverseCtx; +mod loop_head_scope;++use self::loop_head_scope::LoopHeadScope;+ pub fn hook(     transform_async_to_generator: bool,     transform_async_generator_functions: bool,@@ -31,10 +35,21 @@ pub fn hook(     } } +/// Selects the suspension expression used while lowering a `for await` loop.+#[derive(Clone, Copy, Debug)]+enum AwaitForMode {+    NativeAsync,+    Generator,+    AsyncGenerator,+}+ #[derive(Default, Clone, Debug)] struct FnState {     should_transform: bool,     is_generator: bool,+    is_arrow: bool,+    /// The iterator operation form required by this function's output.+    await_for_mode: Option<AwaitForMode>,     use_this: bool,     use_arguments: bool,     use_super: bool,@@ -149,20 +164,36 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {             self.fn_state_stack.push(prev);         } +        let should_transform = function.is_async+            && if function.is_generator {+                self.transform_async_generator_functions+            } else {+                self.transform_async_to_generator+            };+        let await_for_mode = if should_transform {+            Some(if function.is_generator {+                AwaitForMode::AsyncGenerator+            } else {+                AwaitForMode::Generator+            })+        } else if function.is_async && self.transform_async_generator_functions {+            Some(AwaitForMode::NativeAsync)+        } else {+            None+        };+         self.fn_state = Some(FnState {-            should_transform: function.is_async-                && if function.is_generator {-                    self.transform_async_generator_functions-                } else {-                    self.transform_async_to_generator-                },+            should_transform,             is_generator: function.is_generator,+            await_for_mode,             ..Default::default()         });     }      fn exit_arrow_expr(&mut self, arrow_expr: &mut ArrowExpr, _ctx: &mut TraverseCtx) {-        if !arrow_expr.is_async || !self.transform_async_to_generator {+        if !arrow_expr.is_async+            || !(self.transform_async_to_generator || self.transform_async_generator_functions)+        {             return;         } @@ -171,18 +202,24 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {          let fn_state = self.fn_state.take().unwrap(); -        // Restore the previous fn_state from stack+        // Restore the previous fn_state from stack. Preserved async arrows still+        // capture lexical references from a parent that may be transformed.         let parent_fn_state = self.fn_state_stack.pop(); -        // `this`/`arguments`/`super` are inherited from the parent function-        // If arrow is in a constructor and uses `this`, we need to propagate it-        // to use the _this variable pattern at the constructor level-        if let Some(out_fn_state) = &parent_fn_state {-            let mut updated = out_fn_state.clone();-            updated.use_this |= fn_state.use_this;-            updated.use_arguments |= fn_state.use_arguments;-            updated.use_super |= fn_state.use_super;-            self.fn_state = Some(updated);+        if let Some(mut parent_fn_state) = parent_fn_state {+            if fn_state.should_transform+                || parent_fn_state.should_transform+                || parent_fn_state.is_arrow+            {+                parent_fn_state.use_this |= fn_state.use_this;+                parent_fn_state.use_arguments |= fn_state.use_arguments;+                parent_fn_state.use_super |= fn_state.use_super;+            }+            self.fn_state = Some(parent_fn_state);+        }++        if !fn_state.should_transform {+            return;         }          let mut stmts = vec![];@@ -238,7 +275,9 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {     }      fn enter_arrow_expr(&mut self, arrow_expr: &mut ArrowExpr, _ctx: &mut TraverseCtx) {-        if !arrow_expr.is_async || !self.transform_async_to_generator {+        if !arrow_expr.is_async+            || !(self.transform_async_to_generator || self.transform_async_generator_functions)+        {             return;         } @@ -254,8 +293,14 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {         }          self.fn_state = Some(FnState {-            should_transform: true,+            should_transform: self.transform_async_to_generator,             is_generator: false,+            is_arrow: true,+            await_for_mode: Some(if self.transform_async_to_generator {+                AwaitForMode::Generator+            } else {+                AwaitForMode::NativeAsync+            }),             in_constructor,             ..Default::default()         });@@ -322,24 +367,20 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {     }      fn exit_expr(&mut self, expr: &mut Expr, _ctx: &mut TraverseCtx) {-        let Some(-            fn_state @ FnState {-                should_transform: true,-                ..-            },-        ) = &mut self.fn_state-        else {+        let Some(fn_state) = &mut self.fn_state else {             return;         };          match expr {-            Expr::This(..) => {+            Expr::This(..) if fn_state.should_transform || fn_state.is_arrow => {                 fn_state.use_this = true;             }-            Expr::Ident(Ident { sym, .. }) if sym == "arguments" => {+            Expr::Ident(Ident { sym, .. })+                if sym == "arguments" && (fn_state.should_transform || fn_state.is_arrow) =>+            {                 fn_state.use_arguments = true;             }-            Expr::Await(AwaitExpr { arg, span }) => {+            Expr::Await(AwaitExpr { arg, span }) if fn_state.should_transform => {                 *expr = if fn_state.is_generator {                     let callee = helper!(await_async_generator);                     let arg = CallExpr {@@ -367,7 +408,7 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {                 span,                 arg: Some(arg),                 delegate: true,-            }) => {+            }) if fn_state.should_transform => {                 let async_iter =                     helper_expr!(async_iterator).as_call(DUMMY_SP, vec![arg.take().as_arg()]); @@ -387,14 +428,20 @@ impl VisitMutHook<TraverseCtx> for AsyncToGeneratorPass {         }     } +    fn enter_stmt(&mut self, stmt: &mut Stmt, _ctx: &mut TraverseCtx) {+        if matches!(stmt, Stmt::Labeled(..)) {+            if let Some(mode) = self.fn_state.as_ref().and_then(|s| s.await_for_mode) {+                // A label must stay attached to the generated iteration statement.+                handle_await_for(stmt, mode, self.unresolved_ctxt);+            }+        }+    }+     fn exit_stmt(&mut self, stmt: &mut Stmt, _ctx: &mut TraverseCtx) {-        if let Some(FnState {-            should_transform: true,-            is_generator,-            ..-        }) = self.fn_state-        {-            handle_await_for(stmt, is_generator);+        if let Some(mode) = self.fn_state.as_ref().and_then(|s| s.await_for_mode) {+            if matches!(stmt, Stmt::ForOf(..)) {+                handle_await_for(stmt, mode, self.unresolved_ctxt);+            }         }     } @@ -480,14 +527,29 @@ fn could_potentially_throw(param: &[Param], unresolved_ctxt: SyntaxContext) -> b }  #[cfg_attr(debug_assertions, tracing::instrument(level = "debug", skip_all))]-fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {-    let s = match stmt {+fn handle_await_for(stmt: &mut Stmt, mode: AwaitForMode, unresolved_ctxt: SyntaxContext) {+    let mut labels = Vec::new();+    let mut body = &mut *stmt;+    while let Stmt::Labeled(labeled) = body {+        labels.push((labeled.span, labeled.label.clone()));+        body = &mut labeled.body;+    }++    let mut s = match body {         Stmt::ForOf(s @ ForOfStmt { is_await: true, .. }) => s.take(),         _ => return,     };+    let loop_head_scope = if matches!(mode, AwaitForMode::NativeAsync) {+        LoopHeadScope::new(&mut s)+    } else {+        None+    };      let value = private_ident!("_value");     let iterator = private_ident!("_iterator");+    let next_method = matches!(mode, AwaitForMode::NativeAsync).then(|| private_ident!("_next"));+    let return_method =+        matches!(mode, AwaitForMode::NativeAsync).then(|| private_ident!("_return"));     let iterator_error = private_ident!("_iteratorError");     let step = private_ident!("_step");     let did_iteration_error = private_ident!("_didIteratorError");@@ -502,6 +564,77 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {         };          let mut for_loop_body = Vec::new();+        if matches!(mode, AwaitForMode::NativeAsync) {+            // Validate the awaited iterator result before accessing its properties.+            let iter_next: Expr = CallExpr {+                span: DUMMY_SP,+                callee: quote_ident!(unresolved_ctxt, "Reflect")+                    .make_member(quote_ident!("apply"))+                    .as_callee(),+                args: vec![+                    next_method.as_ref().unwrap().clone().as_arg(),+                    iterator.clone().as_arg(),+                    ArrayLit {+                        span: DUMMY_SP,+                        elems: vec![],+                    }+                    .as_arg(),+                ],+                ..Default::default()+            }+            .into();+            for_loop_body.push(+                ExprStmt {+                    span: DUMMY_SP,+                    expr: AssignExpr {+                        span: DUMMY_SP,+                        op: op!("="),+                        left: step.clone().into(),+                        right: await_iteration(iter_next, mode).into(),+                    }+                    .into(),+                }+                .into(),+            );++            for_loop_body.push(check_iterator_object(&step, unresolved_ctxt));++            for_loop_body.push(+                ExprStmt {+                    span: DUMMY_SP,+                    expr: AssignExpr {+                        span: DUMMY_SP,+                        op: op!("="),+                        left: iterator_abrupt_completion.clone().into(),+                        right: UnaryExpr {+                            span: DUMMY_SP,+                            op: op!("!"),+                            arg: step.clone().make_member(quote_ident!("done")).into(),+                        }+                        .into(),+                    }+                    .into(),+                }+                .into(),+            );+            for_loop_body.push(+                IfStmt {+                    span: DUMMY_SP,+                    test: UnaryExpr {+                        span: DUMMY_SP,+                        op: op!("!"),+                        arg: iterator_abrupt_completion.clone().into(),+                    }+                    .into(),+                    cons: Box::new(Stmt::Break(BreakStmt {+                        span: DUMMY_SP,+                        label: None,+                    })),+                    alt: None,+                }+                .into(),+            );+        }         {             // let value = _step.value;             let value_var = VarDeclarator {@@ -534,7 +667,7 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {                 for_loop_body.push(                     VarDecl {                         span: DUMMY_SP,-                        kind: VarDeclKind::Const,+                        kind: v.kind,                         declare: false,                         decls: vec![var_decl],                         ..Default::default()@@ -573,9 +706,10 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {             ..Default::default()         }; +        let mut try_stmts = Vec::new();         let mut init_var_decls = Vec::new();         // _iterator = _async_iterator(lol())-        init_var_decls.push(VarDeclarator {+        let iterator_decl = VarDeclarator {             span: DUMMY_SP,             name: iterator.clone().into(),             init: {@@ -592,7 +726,36 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {                 )             },             definite: false,-        });+        };+        if matches!(mode, AwaitForMode::NativeAsync) {+            // GetIterator rejects a primitive before reading its `next` method.+            // Keep acquisition inside the try block so no close is attempted+            // if either acquisition or validation fails.+            if let Some(loop_head_scope) = loop_head_scope {+                loop_head_scope.init_iterator(iterator_decl, &mut try_stmts);+            } else {+                try_stmts.push(+                    VarDecl {+                        span: DUMMY_SP,+                        kind: VarDeclKind::Var,+                        decls: vec![iterator_decl],+                        ..Default::default()+                    }+                    .into(),+                );+            }+            try_stmts.push(check_iterator_object(&iterator, unresolved_ctxt));+        } else {+            init_var_decls.push(iterator_decl);+        }+        if let Some(next_method) = next_method {+            init_var_decls.push(VarDeclarator {+                span: DUMMY_SP,+                name: next_method.into(),+                init: Some(iterator.clone().make_member(quote_ident!("next")).into()),+                definite: false,+            });+        }         init_var_decls.push(VarDeclarator {             span: DUMMY_SP,             name: step.clone().into(),@@ -600,7 +763,7 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {             definite: false,         }); -        let for_stmt = ForStmt {+        let mut for_stmt: Stmt = ForStmt {             span: s.span,             // var _iterator = _async_iterator(lol()), _step;             init: Some(@@ -614,7 +777,9 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {                 .into(),             ),             // _iteratorAbruptCompletion = !(_step = yield _iterator.next()).done-            test: {+            test: if matches!(mode, AwaitForMode::NativeAsync) {+                None+            } else {                 let iter_next = iterator.clone().make_member(quote_ident!("next"));                 let iter_next = CallExpr {                     span: DUMMY_SP,@@ -623,28 +788,11 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {                     ..Default::default()                 }; -                let yield_arg = if is_async_generator {-                    CallExpr {-                        span: DUMMY_SP,-                        callee: helper!(await_async_generator),-                        args: vec![iter_next.as_arg()],-                        ..Default::default()-                    }-                    .into()-                } else {-                    iter_next.into()-                };-                 let assign_to_step: Expr = AssignExpr {                     span: DUMMY_SP,                     op: op!("="),-                    left: step.into(),-                    right: YieldExpr {-                        span: DUMMY_SP,-                        arg: Some(yield_arg),-                        delegate: false,-                    }-                    .into(),+                    left: step.clone().into(),+                    right: await_iteration(iter_next.into(), mode).into(),                 }                 .into(); @@ -681,9 +829,19 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {         }         .into(); +        for (span, label) in labels.into_iter().rev() {+            for_stmt = LabeledStmt {+                span,+                label,+                body: Box::new(for_stmt),+            }+            .into();+        }++        try_stmts.push(for_stmt);         BlockStmt {             span: body_span,-            stmts: vec![for_stmt],+            stmts: try_stmts,             ..Default::default()         }     };@@ -742,41 +900,76 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {         }         .into(); -        let iterator_return: Expr = CallExpr {-            span: DUMMY_SP,-            callee: iterator+        // GetMethod reads `return` once. Reflect.apply preserves the receiver+        // without consulting a method's own `call` or `apply` properties.+        let return_callee = if return_method.is_some() {+            quote_ident!(unresolved_ctxt, "Reflect")+                .make_member(quote_ident!("apply"))+                .as_callee()+        } else {+            iterator                 .clone()                 .make_member(quote_ident!("return"))-                .as_callee(),-            args: Vec::new(),+                .as_callee()+        };+        let return_args = if let Some(return_method) = &return_method {+            vec![+                return_method.clone().as_arg(),+                iterator.clone().as_arg(),+                ArrayLit {+                    span: DUMMY_SP,+                    elems: vec![],+                }+                .as_arg(),+            ]+        } else {+            vec![]+        };+        let iterator_return: Expr = CallExpr {+            span: DUMMY_SP,+            callee: return_callee,+            args: return_args,             ..Default::default()         }         .into(); -        // yield _iterator.return();-        // or-        // yield _awaitAsyncGenerator(_iterator.return());-        let yield_stmt = ExprStmt {-            span: DUMMY_SP,-            expr: YieldExpr {-                span: DUMMY_SP,-                delegate: false,-                arg: Some(if is_async_generator {-                    CallExpr {+        let awaited_return = await_iteration(iterator_return, mode);+        let close_stmts = if matches!(mode, AwaitForMode::NativeAsync) {+            // AsyncIteratorClose validates the awaited result even though its+            // properties are unused. The inner finally preserves a body error.+            vec![+                ExprStmt {+                    span: DUMMY_SP,+                    expr: AssignExpr {                         span: DUMMY_SP,-                        callee: helper!(await_async_generator),-                        args: vec![iterator_return.as_arg()],-                        ..Default::default()+                        op: op!("="),+                        left: step.clone().into(),+                        right: awaited_return.into(),                     }-                    .into()-                } else {-                    iterator_return.into()-                }),+                    .into(),+                }+                .into(),+                check_iterator_object(&step, unresolved_ctxt),+            ]+        } else {+            vec![ExprStmt {+                span: DUMMY_SP,+                expr: awaited_return.into(),             }-            .into(),-        }-        .into();+            .into()]+        }; +        let get_return = iterator.make_member(quote_ident!("return"));+        let get_return = if let Some(return_method) = &return_method {+            Expr::Assign(AssignExpr {+                span: DUMMY_SP,+                op: op!("="),+                left: return_method.clone().into(),+                right: get_return.into(),+            })+        } else {+            get_return.into()+        };         let conditional_yield = IfStmt {             span: DUMMY_SP,             // _iteratorAbruptCompletion && _iterator.return != null@@ -790,7 +983,7 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {                     BinExpr {                         span: DUMMY_SP,                         op: op!("!="),-                        left: iterator.make_member(quote_ident!("return")).into(),+                        left: get_return.into(),                         right: Null { span: DUMMY_SP }.into(),                     }                     .into(),@@ -798,7 +991,7 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {             }             .into(),             cons: Box::new(Stmt::Block(BlockStmt {-                stmts: vec![yield_stmt],+                stmts: close_stmts,                 ..Default::default()             })),             alt: None,@@ -832,32 +1025,41 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {         finalizer: Some(finally_block),     }; +    let mut decls = vec![+        // var _iteratorAbruptCompletion = false;+        VarDeclarator {+            span: DUMMY_SP,+            name: iterator_abrupt_completion.into(),+            init: Some(false.into()),+            definite: false,+        },+        // var _didIteratorError = false;+        VarDeclarator {+            span: DUMMY_SP,+            name: did_iteration_error.into(),+            init: Some(false.into()),+            definite: false,+        },+        // var _iteratorError;+        VarDeclarator {+            span: DUMMY_SP,+            name: iterator_error.into(),+            init: None,+            definite: false,+        },+    ];+    if let Some(return_method) = return_method {+        decls.push(VarDeclarator {+            span: DUMMY_SP,+            name: return_method.into(),+            init: None,+            definite: false,+        });+    }     let stmts = vec![         VarDecl {             kind: VarDeclKind::Var,-            decls: vec![-                // var _iteratorAbruptCompletion = false;-                VarDeclarator {-                    span: DUMMY_SP,-                    name: iterator_abrupt_completion.into(),-                    init: Some(false.into()),-                    definite: false,-                },-                // var _didIteratorError = false;-                VarDeclarator {-                    span: DUMMY_SP,-                    name: did_iteration_error.into(),-                    init: Some(false.into()),-                    definite: false,-                },-                // var _iteratorError;-                VarDeclarator {-                    span: DUMMY_SP,-                    name: iterator_error.into(),-                    init: None,-                    definite: false,-                },-            ],+            decls,             ..Default::default()         }         .into(),@@ -869,9 +1071,103 @@ fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {         stmts,         ..Default::default()     }+    .into();+}++/// Reject primitive iterator objects and operation results, accepting callable+/// objects without accessing any user-defined properties.+fn check_iterator_object(value: &Ident, unresolved_ctxt: SyntaxContext) -> Stmt {+    let is_not_type = |kind| -> Expr {+        BinExpr {+            span: DUMMY_SP,+            op: op!("!=="),+            left: UnaryExpr {+                span: DUMMY_SP,+                op: op!("typeof"),+                arg: value.clone().into(),+            }+            .into(),+            right: Str {+                span: DUMMY_SP,+                value: kind,+                raw: None,+            }+            .into(),+        }+        .into()+    };+    let non_object = BinExpr {+        span: DUMMY_SP,+        op: op!("||"),+        left: BinExpr {+            span: DUMMY_SP,+            op: op!("==="),+            left: value.clone().into(),+            right: Null { span: DUMMY_SP }.into(),+        }+        .into(),+        right: BinExpr {+            span: DUMMY_SP,+            op: op!("&&"),+            left: is_not_type("object".into()).into(),+            right: is_not_type("function".into()).into(),+        }+        .into(),+    };+    IfStmt {+        span: DUMMY_SP,+        test: non_object.into(),+        cons: Box::new(Stmt::Throw(ThrowStmt {+            span: DUMMY_SP,+            arg: NewExpr {+                span: DUMMY_SP,+                callee: quote_ident!(unresolved_ctxt, "TypeError").into(),+                args: Some(vec![Str {+                    span: DUMMY_SP,+                    value: "Iterator result is not an object".into(),+                    raw: None,+                }+                .as_arg()]),+                ..Default::default()+            }+            .into(),+        })),+        alt: None,+    }     .into() } +/// Wait for an async iterator operation using the surrounding function's form.+fn await_iteration(expr: Expr, mode: AwaitForMode) -> Expr {+    match mode {+        AwaitForMode::NativeAsync => AwaitExpr {+            span: DUMMY_SP,+            arg: expr.into(),+        }+        .into(),+        AwaitForMode::Generator => YieldExpr {+            span: DUMMY_SP,+            arg: Some(expr.into()),+            delegate: false,+        }+        .into(),+        AwaitForMode::AsyncGenerator => {+            let arg = CallExpr {+                span: DUMMY_SP,+                callee: helper!(await_async_generator),+                args: vec![expr.as_arg()],+                ..Default::default()+            };+            YieldExpr {+                span: DUMMY_SP,+                arg: Some(arg.into()),+                delegate: false,+            }+            .into()+        }+    }+}+ /// Replace all `this` expressions with the given identifier in a /// Arrow function body. fn replace_this_in_arrow_function_body(body: &mut ArrowFunctionBody, this_var: &Ident) {

deps/swc/crates/swc_ecma_transformer/src/es2017/async_to_generator/loop_head_scope.rs

new file mode 100644@@ -0,0 +1,106 @@+use rustc_hash::FxHashMap;+use swc_common::DUMMY_SP;+use swc_ecma_ast::*;+use swc_ecma_transforms_base::rename::contains_eval;+use swc_ecma_utils::{find_pat_ids, private_ident, IdentRenamer, IdentUsageFinder};+use swc_ecma_visit::VisitMutWith;++/// The uninitialized lexical bindings visible only to the iterable expression.+/// Per-iteration bindings keep their original identities in the loop body.+pub(super) struct LoopHeadScope {+    bindings: Vec<Ident>,+}++impl LoopHeadScope {+    pub(super) fn new(stmt: &mut ForOfStmt) -> Option<Self> {+        let ForHead::VarDecl(head) = &stmt.left else {+            return None;+        };+        if head.kind == VarDeclKind::Var {+            return None;+        }++        let mut bindings = Vec::new();+        let mut aliases = FxHashMap::default();+        let has_eval = contains_eval(&stmt.right, false);+        for binding in find_pat_ids::<_, Ident>(&head.decls[0].name) {+            // Most iterables do not reference the loop head. Avoid adding a+            // scope or declarations to their output.+            if !has_eval && !IdentUsageFinder::find(&binding, &stmt.right) {+                continue;+            }+            let alias = private_ident!(binding.sym.clone());+            aliases.insert(binding.to_id(), alias.to_id());+            bindings.push(alias);+        }+        if bindings.is_empty() {+            return None;+        }++        stmt.right.visit_mut_with(&mut IdentRenamer::new(&aliases));+        Some(Self { bindings })+    }++    pub(super) fn init_iterator(self, mut iterator: VarDeclarator, stmts: &mut Vec<Stmt>) {+        let init = iterator.init.take().unwrap();+        let iterator_ident = iterator.name.as_ident().unwrap().id.clone();+        stmts.push(+            VarDecl {+                span: DUMMY_SP,+                kind: VarDeclKind::Var,+                decls: vec![iterator],+                ..Default::default()+            }+            .into(),+        );++        // A lexical for-in head creates an uninitialized environment for its+        // RHS. The comma expression acquires the iterator, then evaluates to+        // null, so the loop never initializes its bindings or enters its body.+        // Captured bindings therefore remain in the TDZ after the loop ends.+        // This also preserves awaits and lexical super without a wrapper call,+        // and avoids unreachable declarations that compression can discard.+        let bindings = ArrayPat {+            span: DUMMY_SP,+            elems: self+                .bindings+                .into_iter()+                .map(|id| Some(id.into()))+                .collect(),+            optional: false,+            type_ann: None,+        };+        stmts.push(+            ForInStmt {+                span: DUMMY_SP,+                left: ForHead::VarDecl(Box::new(VarDecl {+                    span: DUMMY_SP,+                    kind: VarDeclKind::Let,+                    decls: vec![VarDeclarator {+                        span: DUMMY_SP,+                        name: bindings.into(),+                        init: None,+                        definite: false,+                    }],+                    ..Default::default()+                })),+                right: SeqExpr {+                    span: DUMMY_SP,+                    exprs: vec![+                        AssignExpr {+                            span: DUMMY_SP,+                            op: op!("="),+                            left: iterator_ident.into(),+                            right: init,+                        }+                        .into(),+                        Null { span: DUMMY_SP }.into(),+                    ],+                }+                .into(),+                body: Box::new(Stmt::Block(BlockStmt::default())),+            }+            .into(),+        );+    }+}

deps/swc/crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs

@@ -165,33 +165,11 @@ impl VisitMutHook<TraverseCtx> for ObjectRestSpreadPass {             return;         } -        // Has rest - create temp for RHS if needed-        let (ref_ident, aliased) = alias_if_required(right, "_ref");--        // Build sequence expression+        // Preserve the RHS value for every extraction and the assignment result.+        // Even an identifier may be overwritten by the pattern or its side effects.         let mut out = ExprOutput::new(ctx);--        if aliased {-            // Declare the temp var-            out.ctx-                .var_declarations-                .insert_var_declarator(VarDeclarator {-                    span: DUMMY_SP,-                    name: ref_ident.clone().into(),-                    init: None,-                    definite: false,-                });-            // _ref = source-            out.exprs.push(-                AssignExpr {-                    span: DUMMY_SP,-                    left: ref_ident.clone().into(),-                    op: op!("="),-                    right: right.take(),-                }-                .into(),-            );-        }+        let ref_ident = out.declare_temp("_ref");+        out.assign(ref_ident.clone().into(), right.take());          let mut lowerer = RestLowerer::new(self.config, out);         lowerer.visit(inner_pat, Box::new(ref_ident.clone().into()));@@ -857,6 +835,11 @@ impl<O: RestOutput> RestLowerer<O> {          // Extract nested pattern and replace with temp         let nested_pat: Pat = match arr.elems[split_idx].as_mut() {+            Some(Pat::Rest(rest)) => {+                let nested = rest.arg.take();+                *rest.arg = split_ref.clone().into();+                *nested+            }             Some(Pat::Assign(assign)) => {                 let nested = assign.left.take();                 *assign.left = split_ref.clone().into();

deps/swc/crates/swc_ecma_transforms/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms" repository    = { workspace = true }-version       = "62.0.0"+version       = "64.0.0"  [package.metadata.docs.rs] all-features = true@@ -39,22 +39,22 @@ typescript = ["swc_ecma_transforms_typescript"] [dependencies] par-core                         = { workspace = true } swc_common                       = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast                     = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transforms_base         = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_compat       = { version = "57.0.1", path = "../swc_ecma_transforms_compat", optional = true, default-features = false }-swc_ecma_transforms_module       = { version = "55.0.1", path = "../swc_ecma_transforms_module", optional = true }-swc_ecma_transforms_optimization = { version = "52.0.0", path = "../swc_ecma_transforms_optimization", optional = true }-swc_ecma_transforms_proposal     = { version = "49.0.1", path = "../swc_ecma_transforms_proposal", optional = true }-swc_ecma_transforms_react        = { version = "55.0.1", path = "../swc_ecma_transforms_react", optional = true }-swc_ecma_transforms_typescript   = { version = "55.0.2", path = "../swc_ecma_transforms_typescript", optional = true }-swc_ecma_utils                   = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast                     = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transforms_base         = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_compat       = { version = "58.0.0", path = "../swc_ecma_transforms_compat", optional = true, default-features = false }+swc_ecma_transforms_module       = { version = "57.0.0", path = "../swc_ecma_transforms_module", optional = true }+swc_ecma_transforms_optimization = { version = "53.0.0", path = "../swc_ecma_transforms_optimization", optional = true }+swc_ecma_transforms_proposal     = { version = "50.0.0", path = "../swc_ecma_transforms_proposal", optional = true }+swc_ecma_transforms_react        = { version = "56.0.0", path = "../swc_ecma_transforms_react", optional = true }+swc_ecma_transforms_typescript   = { version = "56.0.0", path = "../swc_ecma_transforms_typescript", optional = true }+swc_ecma_utils                   = { version = "35.0.3", path = "../swc_ecma_utils" }   [dev-dependencies] par-core = { workspace = true, features = ["chili"] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_base/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_base" repository    = { workspace = true }-version       = "49.0.1"+version       = "50.0.0"  [lib] bench = false@@ -34,18 +34,18 @@ tracing           = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] codspeed-criterion-compat = { workspace = true } par-core                  = { workspace = true, features = ["chili"] } -swc_ecma_codegen           = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_codegen           = { version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_transforms_macros = { version = "1.0.1", path = "../swc_ecma_transforms_macros" } swc_malloc                 = { version = "1.2.5", path = "../swc_malloc" } testing                    = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_base/src/fixer.rs

@@ -800,6 +800,20 @@ impl VisitMut for Fixer<'_> {         self.ctx = old;     } +    fn visit_mut_jsx_expr_container(&mut self, node: &mut JSXExprContainer) {+        let old = self.ctx;+        self.ctx = Context::ForcedExpr;+        node.visit_mut_children_with(self);+        self.ctx = old;+    }++    fn visit_mut_jsx_spread_child(&mut self, node: &mut JSXSpreadChild) {+        let old = self.ctx;+        self.ctx = Context::ForcedExpr;+        node.visit_mut_children_with(self);+        self.ctx = old;+    }+     fn visit_mut_stmt(&mut self, s: &mut Stmt) {         let old = self.ctx;         // only ExprStmt would have unparented expr,

deps/swc/crates/swc_ecma_transforms_classes/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_transforms_classes" repository    = { workspace = true }-version       = "49.0.0"+version       = "50.0.0"  [lib] bench = false@@ -16,7 +16,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }  [dependencies] swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit           = { version = "29.0.0", path = "../swc_ecma_visit" }

deps/swc/crates/swc_ecma_transforms_compat/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_compat" repository    = { workspace = true }-version       = "57.0.1"+version       = "58.0.0"  [lib] bench = false@@ -31,29 +31,29 @@ tracing  = { workspace = true }  swc_atoms                = { version = "10.0.0", path = "../swc_atoms" } swc_common               = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast             = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_compat_bugfixes = { version = "56.0.0", path = "../swc_ecma_compat_bugfixes" }-swc_ecma_compat_common   = { version = "45.0.0", path = "../swc_ecma_compat_common" }-swc_ecma_compat_es2015   = { version = "55.0.0", path = "../swc_ecma_compat_es2015" }-swc_ecma_compat_es2016   = { version = "50.0.0", path = "../swc_ecma_compat_es2016" }-swc_ecma_compat_es2017   = { version = "50.0.0", path = "../swc_ecma_compat_es2017" }-swc_ecma_compat_es2018   = { version = "51.0.0", path = "../swc_ecma_compat_es2018" }-swc_ecma_compat_es2019   = { version = "50.0.0", path = "../swc_ecma_compat_es2019" }-swc_ecma_compat_es2020   = { version = "53.0.0", path = "../swc_ecma_compat_es2020" }-swc_ecma_compat_es2021   = { version = "50.0.0", path = "../swc_ecma_compat_es2021" }-swc_ecma_compat_es2022   = { version = "53.0.0", path = "../swc_ecma_compat_es2022" }-swc_ecma_compat_es3      = { version = "41.0.0", path = "../swc_ecma_compat_es3", optional = true }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils           = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast             = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_compat_bugfixes = { version = "57.0.0", path = "../swc_ecma_compat_bugfixes" }+swc_ecma_compat_common   = { version = "46.0.0", path = "../swc_ecma_compat_common" }+swc_ecma_compat_es2015   = { version = "56.0.0", path = "../swc_ecma_compat_es2015" }+swc_ecma_compat_es2016   = { version = "51.0.0", path = "../swc_ecma_compat_es2016" }+swc_ecma_compat_es2017   = { version = "51.0.0", path = "../swc_ecma_compat_es2017" }+swc_ecma_compat_es2018   = { version = "52.0.0", path = "../swc_ecma_compat_es2018" }+swc_ecma_compat_es2019   = { version = "51.0.0", path = "../swc_ecma_compat_es2019" }+swc_ecma_compat_es2020   = { version = "54.0.0", path = "../swc_ecma_compat_es2020" }+swc_ecma_compat_es2021   = { version = "51.0.0", path = "../swc_ecma_compat_es2021" }+swc_ecma_compat_es2022   = { version = "54.0.0", path = "../swc_ecma_compat_es2022" }+swc_ecma_compat_es3      = { version = "42.0.0", path = "../swc_ecma_compat_es3", optional = true }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils           = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit           = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] par-core   = { workspace = true, features = ["chili"] } serde_json = { workspace = true } -swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_module/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_module" repository    = { workspace = true }-version       = "55.0.1"+version       = "57.0.0"  [lib] bench = false@@ -30,17 +30,17 @@ tracing    = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_config = { version = "6.0.0", path = "../swc_config", features = [+swc_config = { version = "6.0.1", path = "../swc_config", features = [   "file_pattern", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader", features = [ ] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies]@@ -52,7 +52,7 @@ swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader", features =   "node",   "tsc", ] }-swc_ecma_transforms_compat = { version = "57.0.1", path = "../swc_ecma_transforms_compat" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }-swc_ecma_transforms_typescript = { version = "55.0.2", path = "../swc_ecma_transforms_typescript" }+swc_ecma_transforms_compat = { version = "58.0.0", path = "../swc_ecma_transforms_compat" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_transforms_typescript = { version = "56.0.0", path = "../swc_ecma_transforms_typescript" } testing = { version = "27.0.0", path = "../testing/" }

deps/swc/crates/swc_ecma_transforms_module/src/amd.rs

@@ -1,5 +1,6 @@ use std::borrow::Cow; +use pathdiff::diff_paths; use regex::Regex; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize};@@ -41,6 +42,9 @@ pub struct Config {     #[serde(default)]     pub module_id: Option<String>, +    #[serde(default)]+    pub module_root: Option<String>,+     #[serde(flatten, default)]     pub config: InnerConfig, }@@ -61,10 +65,15 @@ pub fn amd<C>( where     C: Comments, {-    let Config { module_id, config } = config;+    let Config {+        module_id,+        module_root,+        config,+    } = config;      visit_mut_pass(Amd {         module_id,+        module_root,         config,         unresolved_mark,         resolver,@@ -92,6 +101,7 @@ where     C: Comments, {     module_id: Option<String>,+    module_root: Option<String>,     config: InnerConfig,     unresolved_mark: Mark,     resolver: Resolver,@@ -118,6 +128,53 @@ where             self.module_id = self.get_amd_module_id_from_comments(n.span);         } +        if self.module_id.is_none() {+            if let Some(module_root) = &self.module_root {+                if let Some(swc_common::FileName::Real(file_path)) = self.resolver.base() {+                    let module_root_path = std::path::Path::new(module_root);+                    let mut relative_path = diff_paths(file_path, module_root_path);++                    let needs_canonicalization = match &relative_path {+                        None => true,+                        Some(p) => p.is_absolute() || p.starts_with(".."),+                    };++                    if needs_canonicalization {+                        let canonical_module_root = module_root_path+                            .canonicalize()+                            .unwrap_or_else(|_| module_root_path.to_path_buf());+                        let canonical_file_path = file_path+                            .canonicalize()+                            .unwrap_or_else(|_| file_path.to_path_buf());++                        if let Some(canonical_relative) =+                            diff_paths(&canonical_file_path, &canonical_module_root)+                        {+                            let use_canonical = match &relative_path {+                                None => true,+                                Some(orig) => {+                                    orig.is_absolute()+                                        || !canonical_relative.starts_with("..")+                                        || canonical_relative.components().count()+                                            < orig.components().count()+                                }+                            };++                            if use_canonical {+                                relative_path = Some(canonical_relative);+                            }+                        }+                    }++                    if let Some(relative_path) = relative_path {+                        if let Some(stem) = relative_path.with_extension("").to_str() {+                            self.module_id = Some(stem.replace('\\', "/"));+                        }+                    }+                }+            }+        }+         if !self.config.allow_top_level_this {             top_level_this(&mut n.body, *Expr::undefined(DUMMY_SP));         }

deps/swc/crates/swc_ecma_transforms_module/src/path.rs

@@ -29,6 +29,13 @@ pub enum Resolver { }  impl Resolver {+    pub(crate) fn base(&self) -> Option<&FileName> {+        match self {+            Self::Real { base, .. } => Some(base),+            Self::Default => None,+        }+    }+     pub(crate) fn resolve(&self, src: Atom) -> Atom {         match self {             Self::Real { resolver, base } => resolver

deps/swc/crates/swc_ecma_transforms_optimization/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_optimization" repository    = { workspace = true }-version       = "52.0.0"+version       = "53.0.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  [lib]@@ -35,12 +35,12 @@ tracing    = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  @@ -48,12 +48,12 @@ swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } par-core = { workspace = true, features = ["chili"] }  -swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] }-swc_ecma_transforms_compat = { version = "57.0.1", path = "../swc_ecma_transforms_compat" }-swc_ecma_transforms_module = { version = "55.0.1", path = "../swc_ecma_transforms_module" }-swc_ecma_transforms_proposal = { version = "49.0.1", path = "../swc_ecma_transforms_proposal" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }-swc_ecma_transforms_typescript = { version = "55.0.2", path = "../swc_ecma_transforms_typescript" }+swc_ecma_transforms_compat = { version = "58.0.0", path = "../swc_ecma_transforms_compat" }+swc_ecma_transforms_module = { version = "57.0.0", path = "../swc_ecma_transforms_module" }+swc_ecma_transforms_proposal = { version = "50.0.0", path = "../swc_ecma_transforms_proposal" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_transforms_typescript = { version = "56.0.0", path = "../swc_ecma_transforms_typescript" } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_optimization/src/simplify/branch/mod.rs

@@ -9,7 +9,8 @@ use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::{cpu_count, Parallel, ParallelExt}; use swc_ecma_utils::{     extract_var_ids, is_literal, prepend_stmt, ExprCtx, ExprExt, ExprFactory, Hoister, IsEmpty,-    StmtExt, StmtLike, Value::Known,+    StmtExt, StmtLike, Type,+    Value::{self, Known}, }; use swc_ecma_visit::{     noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,@@ -136,7 +137,13 @@ impl VisitMut for Remover {                 right,                 ..             }) if match &*left {-                AssignTargetPat::Object(obj) => obj.props.is_empty(),+                AssignTargetPat::Object(obj) => {+                    obj.props.is_empty()+                        && !matches!(+                            right.get_type(self.expr_ctx),+                            Value::Known(Type::Null | Type::Undefined) | Value::Unknown+                        )+                }                 _ => false,             } =>             {

deps/swc/crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs

@@ -40,6 +40,8 @@ pub fn dce(         in_fn: false,         in_non_top_level_lexical_scope: false,         var_decl_kind: None,+        cur_fn_id: None,+        cur_class_id: None,         data: Default::default(),     }) }@@ -87,6 +89,13 @@ struct TreeShaker {     in_non_top_level_lexical_scope: bool,     var_decl_kind: Option<VarDeclKind>, +    /// Innermost enclosing named function / class, tracked exactly like the+    /// [Analyzer] does: references to these from inside their own body are+    /// self-references, which the analyzer never counts, so the [Dropper]+    /// must not subtract them either.+    cur_fn_id: Option<Id>,+    cur_class_id: Option<Id>,+     data: Data, } @@ -238,11 +247,18 @@ impl Data {  /// Graph modification impl Data {-    fn drop_ast_node<N>(&mut self, node: &N)+    /// `cur_fn_id` / `cur_class_id` are the innermost named function / class+    /// enclosing `node`, so that self-references (which [Analyzer::add] never+    /// counted) are not subtracted here.+    fn drop_ast_node<N>(&mut self, node: &N, cur_fn_id: Option<Id>, cur_class_id: Option<Id>)     where         N: for<'aa> VisitWith<Dropper<'aa>>,     {-        let mut dropper = Dropper { data: self };+        let mut dropper = Dropper {+            data: self,+            cur_fn_id,+            cur_class_id,+        };          node.visit_with(&mut dropper);     }@@ -250,6 +266,16 @@ impl Data {  struct Dropper<'a> {     data: &'a mut Data,+    /// Mirrors [Analyzer::cur_fn_id].+    cur_fn_id: Option<Id>,+    /// Mirrors [Analyzer::cur_class_id].+    cur_class_id: Option<Id>,+}++impl Dropper<'_> {+    fn is_self_ref(&self, id: &Id) -> bool {+        self.cur_fn_id.as_ref() == Some(id) || self.cur_class_id.as_ref() == Some(id)+    } }  impl<'a> Visit for Dropper<'a> {@@ -262,22 +288,35 @@ impl<'a> Visit for Dropper<'a> {     }      fn visit_class_decl(&mut self, node: &ClassDecl) {-        node.visit_children_with(self);+        // Same shape as `Analyzer::visit_class_decl`: the superclass is+        // visited outside the class's own binding, the body inside it.+        node.class.super_class.visit_with(self);++        let old = self.cur_class_id.replace(node.ident.to_id());+        node.class.decorators.visit_with(self);+        node.class.body.visit_with(self);+        self.cur_class_id = old;          self.data.drop_assign(&node.ident.to_id());     }      fn visit_class_expr(&mut self, node: &ClassExpr) {-        node.visit_children_with(self);-         if let Some(i) = &node.ident {+            let old = self.cur_class_id.replace(i.to_id());+            node.visit_children_with(self);+            self.cur_class_id = old;+             self.data.drop_assign(&i.to_id());+        } else {+            node.visit_children_with(self);         }     }      fn visit_expr(&mut self, expr: &Expr) {         if let Expr::Ident(i) = expr {-            self.data.drop_usage(&i.to_id());+            if !self.is_self_ref(&i.to_id()) {+                self.data.drop_usage(&i.to_id());+            }             return;         } @@ -285,16 +324,22 @@ impl<'a> Visit for Dropper<'a> {     }      fn visit_fn_decl(&mut self, node: &FnDecl) {+        let old = self.cur_fn_id.replace(node.ident.to_id());         node.visit_children_with(self);+        self.cur_fn_id = old;          self.data.drop_assign(&node.ident.to_id());     }      fn visit_fn_expr(&mut self, node: &FnExpr) {-        node.visit_children_with(self);-         if let Some(i) = &node.ident {+            let old = self.cur_fn_id.replace(i.to_id());+            node.visit_children_with(self);+            self.cur_fn_id = old;+             self.data.drop_assign(&i.to_id());+        } else {+            node.visit_children_with(self);         }     } }@@ -872,6 +917,14 @@ impl Repeated for TreeShaker { }  impl TreeShaker {+    fn drop_ast_node<N>(&mut self, node: &N)+    where+        N: for<'aa> VisitWith<Dropper<'aa>>,+    {+        self.data+            .drop_ast_node(node, self.cur_fn_id.clone(), self.cur_class_id.clone());+    }+     fn visit_mut_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)     where         T: StmtLike + ModuleItemLike + VisitMutWith<Self> + Send + Sync,@@ -959,14 +1012,14 @@ impl TreeShaker {         };          if b.op == op!("&&") && b.left.as_pure_bool(self.expr_ctx) == Known(false) {-            self.data.drop_ast_node(&b.right);+            self.drop_ast_node(&b.right);             *n = *b.left.take();             self.changed = true;             return;         }          if b.op == op!("||") && b.left.as_pure_bool(self.expr_ctx) == Known(true) {-            self.data.drop_ast_node(&b.right);+            self.drop_ast_node(&b.right);             *n = *b.left.take();             self.changed = true;         }@@ -1038,7 +1091,7 @@ impl VisitMut for TreeShaker {                 self.changed = true;                 #[cfg(debug_assertions)]                 debug!("Dropping an assignment to `{}` because it's not used", id);-                self.data.drop_ast_node(&n.left);+                self.drop_ast_node(&n.left);                  n.left.take();             }@@ -1066,6 +1119,27 @@ impl VisitMut for TreeShaker {         self.in_fn = old_in_fn;     } +    fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {+        // Same shape as `Analyzer::visit_class_decl`: the superclass is+        // visited outside the class's own binding, the body inside it.+        n.class.super_class.visit_mut_with(self);++        let old = self.cur_class_id.replace(n.ident.to_id());+        n.class.decorators.visit_mut_with(self);+        n.class.body.visit_mut_with(self);+        self.cur_class_id = old;+    }++    fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) {+        if let Some(i) = &n.ident {+            let old = self.cur_class_id.replace(i.to_id());+            n.visit_mut_children_with(self);+            self.cur_class_id = old;+        } else {+            n.visit_mut_children_with(self);+        }+    }+     fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {         self.visit_mut_par(cpu_count() * 8, members);     }@@ -1079,7 +1153,7 @@ impl VisitMut for TreeShaker {                 debug!("Dropping function `{}` as it's not used", f.ident);                 self.changed = true; -                self.data.drop_ast_node(&*f);+                self.drop_ast_node(&*f);                  n.take();             }@@ -1091,7 +1165,7 @@ impl VisitMut for TreeShaker {                 debug!("Dropping class `{}` as it's not used", c.ident);                 self.changed = true; -                self.data.drop_ast_node(&*c);+                self.drop_ast_node(&*c);                 n.take();             }             _ => {}@@ -1187,6 +1261,22 @@ impl VisitMut for TreeShaker {         }     } +    fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {+        let old = self.cur_fn_id.replace(n.ident.to_id());+        n.visit_mut_children_with(self);+        self.cur_fn_id = old;+    }++    fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {+        if let Some(i) = &n.ident {+            let old = self.cur_fn_id.replace(i.to_id());+            n.visit_mut_children_with(self);+            self.cur_fn_id = old;+        } else {+            n.visit_mut_children_with(self);+        }+    }+     fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {         self.visit_mut_par(cpu_count() * 8, n);     }@@ -1442,7 +1532,7 @@ impl VisitMut for TreeShaker {                 })             {                 for decl in v.decls.iter() {-                    self.data.drop_ast_node(&decl.name);+                    self.drop_ast_node(&decl.name);                 }                  let exprs = v@@ -1536,7 +1626,7 @@ impl VisitMut for TreeShaker {                 self.changed = true;                 #[cfg(debug_assertions)]                 debug!("Dropping {} because it's not used", i);-                self.data.drop_ast_node(&*v);+                self.drop_ast_node(&*v);                 v.name.take();             }         }

deps/swc/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs

@@ -1028,7 +1028,7 @@ pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool)             if let (Known(l), Known(r)) =                 (left.as_pure_wtf8(expr_ctx), right.as_pure_wtf8(expr_ctx))             {-                if left.is_str() || left.is_array_lit() || right.is_str() || right.is_array_lit() {+                if left.is_str() || left.is_array() || right.is_str() || right.is_array() {                     let mut l = l.into_owned();                      l.push_wtf8(&r);

deps/swc/crates/swc_ecma_transforms_proposal/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_proposal" repository    = { workspace = true }-version       = "49.0.1"+version       = "50.0.0"  [lib] bench = false@@ -26,16 +26,16 @@ serde      = { workspace = true, features = ["derive"] }  swc_atoms                   = { version = "10.0.0", path = "../swc_atoms" } swc_common                  = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast                = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transforms_base    = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_classes = { version = "49.0.0", path = "../swc_ecma_transforms_classes" }-swc_ecma_utils              = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast                = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transforms_base    = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_classes = { version = "50.0.0", path = "../swc_ecma_transforms_classes" }+swc_ecma_utils              = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit              = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] serde_json                  = { workspace = true }-swc_ecma_compat_es2022      = { version = "53.0.0", path = "../swc_ecma_compat_es2022" }-swc_ecma_parser             = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms_compat  = { version = "57.0.1", path = "../swc_ecma_transforms_compat" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_compat_es2022      = { version = "54.0.0", path = "../swc_ecma_compat_es2022" }+swc_ecma_parser             = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms_compat  = { version = "58.0.0", path = "../swc_ecma_transforms_compat" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" } testing                     = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_proposal/src/export_default_from.rs

@@ -1,19 +1,22 @@ use swc_common::DUMMY_SP; use swc_ecma_ast::*; use swc_ecma_utils::quote_ident;-use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut};  /// `@babel/plugin-proposal-export-default-from` pub fn export_default_from() -> impl Pass {-    visit_mut_pass(ExportDefaultFrom)+    ExportDefaultFrom }  struct ExportDefaultFrom; -impl VisitMut for ExportDefaultFrom {-    noop_visit_mut_type!();+impl Pass for ExportDefaultFrom {+    fn process(&mut self, program: &mut Program) {+        let Program::Module(module) = program else {+            return;+        }; -    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {+        // Export-default-from only needs the module's top-level items.+        let items = &mut module.body;         let count = items             .iter()             .filter(|m| {

deps/swc/crates/swc_ecma_transforms_react/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_react" repository    = { workspace = true }-version       = "55.0.1"+version       = "56.0.0"  [lib] bench = false@@ -33,19 +33,19 @@ sha1       = { workspace = true } string_enum                = { version = "1.0.2", path = "../string_enum" } swc_atoms                  = { version = "10.0.0", path = "../swc_atoms" } swc_common                 = { version = "26.0.0", path = "../swc_common" }-swc_config                 = { version = "6.0.0", path = "../swc_config" }-swc_ecma_ast               = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_config                 = { version = "6.0.1", path = "../swc_config" }+swc_ecma_ast               = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_hooks             = { version = "1.0.0", path = "../swc_ecma_hooks" }-swc_ecma_parser            = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser            = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base   = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_compat = { version = "57.0.1", path = "../swc_ecma_transforms_compat/", optional = true }-swc_ecma_utils             = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms_base   = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_compat = { version = "58.0.0", path = "../swc_ecma_transforms_compat/", optional = true }+swc_ecma_utils             = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit             = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies]-swc_ecma_codegen            = { version = "32.0.0", path = "../swc_ecma_codegen/" }-swc_ecma_transforms_compat  = { version = "57.0.1", path = "../swc_ecma_transforms_compat/" }-swc_ecma_transforms_testing = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_codegen            = { version = "32.0.3", path = "../swc_ecma_codegen/" }+swc_ecma_transforms_compat  = { version = "58.0.0", path = "../swc_ecma_transforms_compat/" }+swc_ecma_transforms_testing = { version = "54.0.0", path = "../swc_ecma_transforms_testing" } testing                     = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_ecma_transforms_testing/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_transforms_testing" repository    = { workspace = true }-version       = "53.0.0"+version       = "54.0.0"  [lib] bench = false@@ -22,12 +22,12 @@ sha2 = { workspace = true } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_testing = { version = "27.0.0", path = "../swc_ecma_testing" }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_testing = { version = "27.0.1", path = "../swc_ecma_testing" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_sourcemap = { workspace = true } tempfile = { workspace = true }

deps/swc/crates/swc_ecma_transforms_typescript/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"] license       = { workspace = true } name          = "swc_ecma_transforms_typescript" repository    = { workspace = true }-version       = "55.0.2"+version       = "56.0.0"  [lib] bench = false@@ -26,21 +26,21 @@ serde      = { workspace = true, features = ["derive"] }  swc_atoms                 = { version = "10.0.0", path = "../swc_atoms" } swc_common                = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast              = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_transforms_base  = { version = "49.0.1", path = "../swc_ecma_transforms_base" }-swc_ecma_transforms_react = { version = "55.0.1", path = "../swc_ecma_transforms_react" }-swc_ecma_utils            = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_ast              = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_transforms_base  = { version = "50.0.0", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_react = { version = "56.0.0", path = "../swc_ecma_transforms_react" }+swc_ecma_utils            = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit            = { version = "29.0.0", path = "../swc_ecma_visit" }  [dev-dependencies] codspeed-criterion-compat = { workspace = true } -swc_ecma_codegen             = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_codegen             = { version = "32.0.3", path = "../swc_ecma_codegen" } serde_json                   = { workspace = true }-swc_ecma_parser              = { version = "45.1.1", path = "../swc_ecma_parser", features = ["flow"] }-swc_ecma_transforms_compat   = { version = "57.0.1", path = "../swc_ecma_transforms_compat" }-swc_ecma_transforms_proposal = { version = "49.0.1", path = "../swc_ecma_transforms_proposal" }-swc_ecma_transforms_testing  = { version = "53.0.0", path = "../swc_ecma_transforms_testing" }+swc_ecma_parser              = { version = "46.0.0", path = "../swc_ecma_parser", features = ["flow"] }+swc_ecma_transforms_compat   = { version = "58.0.0", path = "../swc_ecma_transforms_compat" }+swc_ecma_transforms_proposal = { version = "50.0.0", path = "../swc_ecma_transforms_proposal" }+swc_ecma_transforms_testing  = { version = "54.0.0", path = "../swc_ecma_transforms_testing" } testing                      = { version = "27.0.0", path = "../testing" }  [[bench]]

deps/swc/crates/swc_ecma_transforms_typescript/src/ts_enum.rs

@@ -198,6 +198,7 @@ impl EnumValueComputer<'_> {                 | Expr::Unary(..)                 | Expr::Bin(..)                 | Expr::Member(..)+                | Expr::OptChain(..)                 | Expr::Tpl(..)                 | Expr::TsAs(..)                 | Expr::TsNonNull(..)@@ -266,6 +267,19 @@ impl EnumValueComputer<'_> {             Expr::Unary(e) => self.compute_unary(e, ctx),             Expr::Bin(e) => self.compute_bin(e, ctx),             Expr::Member(e) => self.compute_member(e, ctx),+            Expr::OptChain(e) => {+                let opaque_expr = TsEnumRecordValue::Opaque(e.clone().into());+                let OptChainBase::Member(member) = *e.base else {+                    return opaque_expr;+                };+                // `compute_member` builds its own fallback from the+                // `MemberExpr` alone, which would drop the `?.` and turn a+                // guarded read into an unguarded one.+                match self.compute_member(member, ctx) {+                    TsEnumRecordValue::Opaque(..) => opaque_expr,+                    value => value,+                }+            }             Expr::Tpl(e) => self.compute_tpl(e, ctx),             // Handle TypeScript type expressions by stripping them             // and computing the inner expression

deps/swc/crates/swc_ecma_utils/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecma_utils" repository    = { workspace = true }-version       = "35.0.1"+version       = "35.0.3"  [package.metadata.docs.rs] all-features = true@@ -33,7 +33,7 @@ tracing        = { workspace = true }  swc_atoms      = { version = "10.0.0", path = "../swc_atoms" } swc_common     = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast   = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast   = { version = "29.0.2", path = "../swc_ecma_ast" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }  [target.'cfg(not(any(target_arch = "wasm32", target_arch = "arm")))'.dependencies]@@ -41,4 +41,4 @@ stacker = { version = "0.1.15", optional = true }  [dev-dependencies] par-core        = { workspace = true, features = ["chili"] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" }

deps/swc/crates/swc_ecma_utils/src/lib.rs

@@ -623,7 +623,16 @@ pub trait StmtExt {                 Stmt::Decl(decl) => match decl {                     Decl::Class(class_decl) => class_has_side_effect(ctx, &class_decl.class),                     Decl::Fn(_) => !ctx.in_strict,-                    Decl::Var(var_decl) => var_decl.kind == VarDeclKind::Var,+                    Decl::Var(var_decl) => {+                        var_decl.kind == VarDeclKind::Var+                            || var_decl.decls.iter().any(|decl| {+                                decl.init+                                    .as_ref()+                                    .map(|init| init.may_have_side_effects(ctx))+                                    .unwrap_or(false)+                                    || !decl.name.is_ident()+                            })+                    }                     _ => false,                 },                 Stmt::Expr(expr_stmt) => expr_stmt.expr.may_have_side_effects(ctx),@@ -737,8 +746,9 @@ pub trait ExprExt {     }      #[inline(always)]+    #[deprecated(note = "please use `is_array` in `Expr` directly")]     fn is_array_lit(&self) -> bool {-        is_array_lit(self.as_expr())+        self.as_expr().is_array()     }      /// Checks if `self` is `NaN`.@@ -857,14 +867,14 @@ pub fn class_has_side_effect(expr_ctx: ExprCtx, c: &Class) -> bool {                 }                  if let Some(v) = &p.value {-                    if v.may_have_side_effects(expr_ctx) {+                    if p.is_static && v.may_have_side_effects(expr_ctx) {                         return true;                     }                 }             }             ClassMember::PrivateProp(p) => {                 if let Some(v) = &p.value {-                    if v.may_have_side_effects(expr_ctx) {+                    if p.is_static && v.may_have_side_effects(expr_ctx) {                         return true;                     }                 }@@ -2812,10 +2822,6 @@ fn is_str(expr: &Expr) -> bool {     } } -fn is_array_lit(expr: &Expr) -> bool {-    matches!(*expr, Expr::Array(..))-}- fn is_nan(expr: &Expr) -> bool {     match expr {         Expr::Lit(Lit::Num(number)) => number.value.is_nan(),@@ -3349,9 +3355,11 @@ fn get_type(expr: &Expr, ctx: ExprCtx) -> Value<Type> {             ..         }) if &**length == "length" => match &**obj {             Expr::Array(ArrayLit { .. }) | Expr::Lit(Lit::Str(..)) => Known(Type::Num),-            Expr::Ident(Ident { sym: arguments, .. }) if &**arguments == "arguments" => {-                Known(Type::Num)-            }+            Expr::Ident(Ident {+                sym: arguments,+                ctxt,+                ..+            }) if &**arguments == "arguments" && *ctxt == ctx.unresolved_ctxt => Known(Type::Num),             _ => Unknown,         }, @@ -3424,9 +3432,9 @@ fn get_type(expr: &Expr, ctx: ExprCtx) -> Value<Type> {             Unknown         } -        Expr::Ident(Ident { ref sym, .. }) => Known(match &**sym {-            "undefined" => UndefinedType,-            "NaN" | "Infinity" => NumberType,+        Expr::Ident(Ident { sym, ctxt, .. }) => Known(match &**sym {+            "undefined" if *ctxt == ctx.unresolved_ctxt => UndefinedType,+            "NaN" | "Infinity" if *ctxt == ctx.unresolved_ctxt => NumberType,             _ => return Unknown,         }), @@ -3583,9 +3591,12 @@ fn is_pure_callee(expr: &Expr, ctx: ExprCtx) -> bool { /// - But `new (class {})()` can be pure if the class has no side effects fn is_pure_new_callee(expr: &Expr, ctx: ExprCtx) -> bool {     match expr {-        // An empty function expression is also pure for `new`+        // Only an empty non-async, non-generator function expression is pure for `new`.+        // Async and generator functions are not constructible, so `new` throws.         Expr::Fn(FnExpr { function: f, .. })-            if f.params.iter().all(|p| p.pat.is_ident())+            if !f.is_async+                && !f.is_generator+                && f.params.iter().all(|p| p.pat.is_ident())                 && f.body.is_some()                 && f.body.as_ref().unwrap().stmts.is_empty() =>         {@@ -3616,6 +3627,9 @@ fn is_pure_new_callee(expr: &Expr, ctx: ExprCtx) -> bool {                 match member {                     ClassMember::ClassProp(p) if !p.is_static => return false,                     ClassMember::PrivateProp(p) if !p.is_static => return false,+                    // Auto-accessor initializers run when the instance is constructed,+                    // just like instance field initializers.+                    ClassMember::AutoAccessor(p) if !p.is_static => return false,                     _ => {}                 }             }@@ -3623,6 +3637,14 @@ fn is_pure_new_callee(expr: &Expr, ctx: ExprCtx) -> bool {             // Check constructor - must be empty or not present             for member in &class.body {                 if let ClassMember::Constructor(ctor) = member {+                    if !ctor+                        .params+                        .iter()+                        .all(|param| param.as_param().is_some_and(|param| param.pat.is_ident()))+                    {+                        return false;+                    }+                     if let Some(body) = &ctor.body {                         if !body.stmts.is_empty() {                             return false;

deps/swc/crates/swc_ecma_visit/Cargo.toml

@@ -32,5 +32,5 @@ tracing               = { workspace = true }  swc_atoms    = { version = "10.0.0", path = "../swc_atoms" } swc_common   = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" } swc_visit    = { version = "2.0.2", path = "../swc_visit" }

deps/swc/crates/swc_ecmascript/Cargo.toml

@@ -6,7 +6,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_ecmascript" repository    = { workspace = true }-version       = "70.1.0"+version       = "74.0.0"  [package.metadata.docs.rs] all-features = true@@ -41,14 +41,14 @@ react        = ["swc_ecma_transforms/react"] typescript   = ["typescript-parser", "swc_ecma_transforms/typescript"]  [dependencies]-swc_ecma_ast        = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen    = { version = "32.0.0", path = "../swc_ecma_codegen", optional = true }-swc_ecma_minifier   = { version = "61.0.5", path = "../swc_ecma_minifier", optional = true }-swc_ecma_parser     = { version = "45.1.1", path = "../swc_ecma_parser", optional = true, default-features = false }-swc_ecma_preset_env = { version = "63.0.0", path = "../swc_ecma_preset_env", optional = true }-swc_ecma_quote      = { version = "45.0.0", path = "../swc_ecma_quote", optional = true }-swc_ecma_transforms = { version = "62.0.0", path = "../swc_ecma_transforms", optional = true }-swc_ecma_utils      = { version = "35.0.1", path = "../swc_ecma_utils", optional = true }+swc_ecma_ast        = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen    = { version = "32.0.3", path = "../swc_ecma_codegen", optional = true }+swc_ecma_minifier   = { version = "63.0.1", path = "../swc_ecma_minifier", optional = true }+swc_ecma_parser     = { version = "46.0.0", path = "../swc_ecma_parser", optional = true, default-features = false }+swc_ecma_preset_env = { version = "66.0.0", path = "../swc_ecma_preset_env", optional = true }+swc_ecma_quote      = { version = "46.0.0", path = "../swc_ecma_quote", optional = true }+swc_ecma_transforms = { version = "64.0.0", path = "../swc_ecma_transforms", optional = true }+swc_ecma_utils      = { version = "35.0.3", path = "../swc_ecma_utils", optional = true } swc_ecma_visit      = { version = "29.0.0", path = "../swc_ecma_visit", optional = true }  [dev-dependencies]

deps/swc/crates/swc_estree_ast/Cargo.toml

@@ -9,7 +9,7 @@ edition = "2021" license = { workspace = true } name = "swc_estree_ast" repository = "https://github.com/swc-project/swc.git"-version = "26.0.0"+version = "27.0.0"  [package.metadata.docs.rs] all-features = true

deps/swc/crates/swc_estree_ast/src/class.rs

@@ -220,6 +220,8 @@ pub struct ClassPrivateProperty {     pub value: Option<Box<Expression>>,     #[serde(default)]     pub decorators: Option<Vec<Decorator>>,+    #[serde(default)]+    pub computed: bool,     #[serde(default, rename = "static")]     pub static_any: Value,     #[serde(default)]

deps/swc/crates/swc_estree_ast/src/lit.rs

@@ -176,13 +176,27 @@ enum AcornLiteralValue {     Decimal(Atom), } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]+#[serde(rename_all = "camelCase")]+pub struct LiteralExtra<T> {+    pub raw: Atom,+    pub raw_value: T,+}++pub type StringLiteralExtra = LiteralExtra<Wtf8Atom>;+pub type NumericLiteralExtra = LiteralExtra<f64>;+pub type BigIntLiteralExtra = LiteralExtra<String>;+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] pub struct StringLiteral {     #[serde(flatten)]     pub base: BaseNode,     pub value: Wtf8Atom,+    #[serde(default)]     pub raw: Atom,+    #[serde(default, skip_serializing_if = "Option::is_none")]+    pub extra: Option<StringLiteralExtra>, }  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]@@ -191,6 +205,8 @@ pub struct NumericLiteral {     #[serde(flatten)]     pub base: BaseNode,     pub value: f64,+    #[serde(default, skip_serializing_if = "Option::is_none")]+    pub extra: Option<NumericLiteralExtra>, }  /// Deprecated. Use NumericLiteral instead.@@ -319,6 +335,8 @@ pub struct BigIntLiteral {     pub value: String,     #[serde(default)]     pub raw: Atom,+    #[serde(default, skip_serializing_if = "Option::is_none")]+    pub extra: Option<BigIntLiteralExtra>, }  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]

deps/swc/crates/swc_estree_ast/src/module.rs

@@ -101,10 +101,11 @@ pub struct Program {     pub source_file: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "lowercase")] pub enum ExportKind {     Type,+    #[default]     Value, } @@ -116,6 +117,7 @@ pub struct ExportSpecifier {     pub base: BaseNode,     pub local: ModuleExportNameType,     pub exported: ModuleExportNameType,+    #[serde(default)]     pub export_kind: ExportKind, } 

deps/swc/crates/swc_estree_ast/src/stmt.rs

@@ -291,6 +291,8 @@ pub struct ForOfStatement {     pub left: ForStmtLeft,     pub right: Box<Expression>,     pub body: Box<Statement>,+    #[serde(default, rename = "await")]+    pub is_await: bool, }  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

deps/swc/crates/swc_estree_compat/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_estree_compat" repository    = { workspace = true }-version       = "48.0.0"+version       = "49.0.0"  [package.metadata.docs.rs] all-features = true@@ -27,27 +27,24 @@ serde      = { workspace = true, features = ["derive"] } serde_json = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" }-swc_common = { version = "26.0.0", path = "../swc_common", features = [-  "sourcemap",-  "tty-emitter",-] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_common = { version = "26.0.0", path = "../swc_common" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" }-swc_estree_ast = { version = "26.0.0", path = "../swc_estree_ast" }+swc_estree_ast = { version = "27.0.0", path = "../swc_estree_ast" } swc_node_comments = { version = "27.0.0", path = "../swc_node_comments/" }  [dev-dependencies] codspeed-criterion-compat = { workspace = true } pretty_assertions         = { workspace = true } -swc                 = { version = "76.0.0", path = "../swc", default-features = false }-swc_ecma_ast        = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_parser     = { version = "45.1.1", path = "../swc_ecma_parser" }-swc_ecma_transforms = { version = "62.0.0", path = "../swc_ecma_transforms/" }+swc                 = { version = "80.0.0", path = "../swc", default-features = false }+swc_ecma_ast        = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_parser     = { version = "46.0.0", path = "../swc_ecma_parser" }+swc_ecma_transforms = { version = "64.0.0", path = "../swc_ecma_transforms/" } testing             = { version = "27.0.0", path = "../testing" } walkdir             = { workspace = true } 

deps/swc/crates/swc_estree_compat/src/babelify/class.rs

@@ -18,9 +18,23 @@ impl Babelify for Class {     type Output = ClassExpression;      fn babelify(self, ctx: &Context) -> Self::Output {+        let mut body_base = ctx.base(extract_class_body_span(&self, ctx));+        let mut members = Vec::with_capacity(self.body.len());+        for m in self.body {+            match m {+                ClassMember::Empty(empty) => {+                    let empty_base = ctx.base(empty.span);+                    body_base.inner_comments.extend(empty_base.leading_comments);+                    body_base+                        .inner_comments+                        .extend(empty_base.trailing_comments);+                }+                other => members.push(other),+            }+        }         let body = ClassBody {-            base: ctx.base(extract_class_body_span(&self, ctx)),-            body: self.body.babelify(ctx),+            base: body_base,+            body: members.babelify(ctx),         };          ClassExpression {@@ -110,6 +124,7 @@ impl Babelify for PrivateProp {             type_annotation: self                 .type_ann                 .map(|ann| Box::alloc().init(ann.babelify(ctx).into())),+            computed: false,             static_any: Value::Bool(self.is_static),             decorators: Some(self.decorators.babelify(ctx)),         }@@ -120,6 +135,8 @@ impl Babelify for ClassMethod {     type Output = BabelClassMethod;      fn babelify(self, ctx: &Context) -> Self::Output {+        let computed = Some(self.key.is_computed());+         let params = babelify_function_params(self.function.this_param, self.function.params, ctx);          BabelClassMethod {@@ -141,7 +158,7 @@ impl Babelify for ClassMethod {                 .function                 .return_type                 .map(|t| Box::alloc().init(t.babelify(ctx).into())),-            computed: Default::default(),+            computed,         }     } }@@ -171,7 +188,7 @@ impl Babelify for PrivateMethod {                 .function                 .return_type                 .map(|t| Box::alloc().init(t.babelify(ctx).into())),-            computed: Default::default(),+            computed: Some(false),         }     } }@@ -180,6 +197,8 @@ impl Babelify for Constructor {     type Output = BabelClassMethod;      fn babelify(self, ctx: &Context) -> Self::Output {+        let computed = Some(self.key.is_computed());+         BabelClassMethod {             base: ctx.base(self.span),             kind: Some(ClassMethodKind::Constructor),@@ -189,10 +208,10 @@ impl Babelify for Constructor {             access: self.accessibility.map(|access| access.babelify(ctx)),             accessibility: self.accessibility.map(|access| access.babelify(ctx)),             optional: Some(self.is_optional),-            computed: Default::default(),-            is_static: Default::default(),-            generator: Default::default(),-            is_async: Default::default(),+            computed,+            is_static: Some(false),+            generator: Some(false),+            is_async: Some(false),             is_abstract: Default::default(),             decorators: Default::default(),             return_type: Default::default(),@@ -236,3 +255,69 @@ impl Babelify for StaticBlock {         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{+        comments::{Comment, CommentKind, Comments},+        sync::Lrc,+        BytePos, FileName, SourceMap, Span, SyntaxContext, DUMMY_SP,+    };+    use swc_ecma_ast::{Class, ClassMember, ClassProp, EmptyStmt, IdentName, PropName};+    use swc_node_comments::SwcComments;++    use crate::babelify::{Babelify, Context};++    #[test]+    fn test_class_with_empty_member() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), ";");+        let comments = SwcComments::default();+        let empty_span = Span::new(BytePos(1), BytePos(2));+        comments.add_leading(+            empty_span.lo,+            Comment {+                kind: CommentKind::Block,+                span: DUMMY_SP,+                text: atom!(" empty stmt comment "),+            },+        );+        let ctx = Context { fm, cm, comments };++        let class = Class {+            span: DUMMY_SP,+            ctxt: SyntaxContext::empty(),+            decorators: vec![],+            body: vec![+                ClassMember::Empty(EmptyStmt { span: empty_span }),+                ClassMember::ClassProp(ClassProp {+                    span: DUMMY_SP,+                    key: PropName::Ident(IdentName {+                        span: DUMMY_SP,+                        sym: atom!("x"),+                    }),+                    value: None,+                    type_ann: None,+                    is_static: false,+                    decorators: vec![],+                    accessibility: None,+                    is_abstract: false,+                    is_override: false,+                    is_optional: false,+                    readonly: false,+                    declare: false,+                    definite: false,+                }),+            ],+            super_class: None,+            is_abstract: false,+            type_params: None,+            super_type_params: None,+            implements: vec![],+        };+        let class_expr = class.babelify(&ctx);+        assert_eq!(class_expr.body.body.len(), 1);+        assert_eq!(class_expr.body.base.inner_comments.len(), 1);+    }+}

deps/swc/crates/swc_estree_compat/src/babelify/expr.rs

@@ -5,9 +5,10 @@ use swc_common::{BytePos, Span, Spanned}; use swc_ecma_ast::{     ArrayLit, ArrowExpr, ArrowFunctionBody, AssignExpr, AssignTarget, AssignTargetPat, AwaitExpr,     BinExpr, BinaryOp, CallExpr, Callee, ClassExpr, CondExpr, Expr, ExprOrSpread, FnExpr, Ident,-    Import, Lit, MemberExpr, MemberProp, MetaPropExpr, MetaPropKind, NewExpr, ObjectLit, ParenExpr,-    PropOrSpread, SeqExpr, SimpleAssignTarget, SpreadElement, Super, SuperProp, SuperPropExpr,-    TaggedTpl, ThisExpr, Tpl, TplElement, UnaryExpr, UpdateExpr, YieldExpr,+    Import, Lit, MemberExpr, MemberProp, MetaPropExpr, MetaPropKind, NewExpr, ObjectLit,+    OptChainBase, OptChainExpr, ParenExpr, PropOrSpread, SeqExpr, SimpleAssignTarget,+    SpreadElement, Super, SuperProp, SuperPropExpr, TaggedTpl, ThisExpr, Tpl, TplElement,+    UnaryExpr, UpdateExpr, YieldExpr, }; use swc_estree_ast::{     flavor::Flavor, ArrayExprEl, ArrayExpression, ArrowFuncExprBody, ArrowFunctionExpression,@@ -15,6 +16,7 @@ use swc_estree_ast::{     Callee as BabelCallee, ClassExpression, ConditionalExpression, Expression, FunctionExpression,     Import as BabelImport, LVal, Literal, LogicalExpression, MemberExprProp, MemberExpression,     MetaProperty, NewExpression, ObjectExprProp, ObjectExpression, ObjectKey, ObjectMember,+    OptionalCallExpression, OptionalMemberExprProp, OptionalMemberExpression,     ParenthesizedExpression, PrivateName, SequenceExpression, SpreadElement as BabelSpreadElement,     Super as BabelSuper, TaggedTemplateExprTypeParams, TaggedTemplateExpression, TemplateElVal,     TemplateElement, TemplateLiteral, TemplateLiteralExpr, ThisExpression, UnaryExpression,@@ -163,10 +165,7 @@ impl Babelify for Expr {                 "illegal conversion: Cannot convert {:?} to ExprOutput - babel has no equivalent",                 &self             ),-            Expr::OptChain(_) => panic!(-                "illegal conversion: Cannot convert {:?} to ExprOutput - babel has no equivalent",-                &self-            ),+            Expr::OptChain(o) => ExprOutput::Expr(Box::alloc().init(o.babelify(ctx))),             Expr::Invalid(_) => panic!(                 "illegal conversion: Cannot convert {:?} to ExprOutput - babel has no equivalent",                 &self@@ -778,14 +777,127 @@ impl Babelify for AssignTargetPat {     } } -// NOTE: OptChainExpr does not appear to have an official Babel AST node yet.+impl Babelify for OptChainExpr {+    type Output = Expression;++    fn babelify(self, ctx: &Context) -> Self::Output {+        match *self.base {+            OptChainBase::Member(m) => {+                let computed = matches!(m.prop, MemberProp::Computed(_));+                let property = match m.prop {+                    MemberProp::Ident(i) => {+                        OptionalMemberExprProp::Id(Ident::from(i).babelify(ctx))+                    }+                    MemberProp::PrivateName(p) => OptionalMemberExprProp::Id(p.babelify(ctx).id),+                    MemberProp::Computed(c) => {+                        OptionalMemberExprProp::Expr(Box::alloc().init(c.babelify(ctx)))+                    }+                    #[cfg(swc_ast_unknown)]+                    _ => panic!("unable to access unknown nodes"),+                };+                Expression::OptionalMember(OptionalMemberExpression {+                    base: ctx.base(self.span),+                    object: Box::alloc().init(m.obj.babelify(ctx).into()),+                    property,+                    computed,+                    optional: self.optional,+                })+            }+            OptChainBase::Call(c) => {+                let arguments = c+                    .args+                    .into_iter()+                    .map(|arg| arg.babelify(ctx).into())+                    .collect();+                let type_parameters = c.type_args.map(|t| t.babelify(ctx));+                Expression::OptionalCall(OptionalCallExpression {+                    base: ctx.base(self.span),+                    callee: Box::alloc().init(c.callee.babelify(ctx).into()),+                    arguments,+                    optional: self.optional,+                    type_arguments: None,+                    type_parameters,+                })+            }+            #[cfg(swc_ast_unknown)]+            _ => panic!("unable to access unknown nodes"),+        }+    }+} -// #[ast_node("OptionalChainingExpression")]-// #[derive(Eq, Hash, EqIgnoreSpan)]-// #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]-// pub struct OptChainExpr {-//     pub span: Span,-//     pub question_dot_token: Span,-//     pub expr: Box<Expr>,-// }-//+#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{sync::Lrc, FileName, SourceMap, DUMMY_SP};+    use swc_ecma_ast::{+        Expr, Ident, IdentName, MemberExpr, MemberProp, OptCall, OptChainBase, OptChainExpr,+    };+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::babelify::{Babelify, Context};++    fn test_ctx() -> Context {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        Context {+            fm,+            cm,+            comments: SwcComments::default(),+        }+    }++    #[test]+    fn test_opt_chain_member() {+        let ctx = test_ctx();+        let opt_member = Expr::OptChain(OptChainExpr {+            span: DUMMY_SP,+            optional: true,+            base: Box::new(OptChainBase::Member(MemberExpr {+                span: DUMMY_SP,+                obj: Box::new(Expr::Ident(Ident::new_no_ctxt(atom!("a"), DUMMY_SP))),+                prop: MemberProp::Ident(IdentName::new(atom!("b"), DUMMY_SP)),+            })),+        });++        let expr = opt_member.babelify(&ctx);+        match expr {+            super::ExprOutput::Expr(expr) => match *expr {+                estree::Expression::OptionalMember(om) => {+                    assert!(om.optional);+                    assert!(!om.computed);+                }+                _ => panic!("Expected Expression::OptionalMember"),+            },+            _ => panic!("Expected ExprOutput::Expr"),+        }+    }++    #[test]+    fn test_opt_chain_call() {+        let ctx = test_ctx();+        let opt_call = Expr::OptChain(OptChainExpr {+            span: DUMMY_SP,+            optional: true,+            base: Box::new(OptChainBase::Call(OptCall {+                span: DUMMY_SP,+                callee: Box::new(Expr::Ident(Ident::new_no_ctxt(atom!("fn"), DUMMY_SP))),+                args: vec![],+                type_args: None,+                ctxt: Default::default(),+            })),+        });++        let expr = opt_call.babelify(&ctx);+        match expr {+            super::ExprOutput::Expr(expr) => match *expr {+                estree::Expression::OptionalCall(oc) => {+                    assert!(oc.optional);+                    assert!(oc.arguments.is_empty());+                }+                _ => panic!("Expected Expression::OptionalCall"),+            },+            _ => panic!("Expected ExprOutput::Expr"),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/babelify/lit.rs

@@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use swc_atoms::atom; use swc_ecma_ast::{BigInt, Bool, Lit, Null, Number, Regex, Str}; use swc_estree_ast::{-    BigIntLiteral, BooleanLiteral, JSXText as BabelJSXText, Literal, NullLiteral, NumericLiteral,-    RegExpLiteral, StringLiteral,+    BigIntLiteral, BigIntLiteralExtra, BooleanLiteral, JSXText as BabelJSXText, Literal,+    NullLiteral, NumericLiteral, NumericLiteralExtra, RegExpLiteral, StringLiteral,+    StringLiteralExtra, };  use crate::babelify::{Babelify, Context};@@ -36,14 +37,21 @@ impl Babelify for Str {     type Output = StringLiteral;      fn babelify(self, ctx: &Context) -> Self::Output {+        let raw = match self.raw {+            Some(value) => value,+            None => serde_json::to_string(&self.value)+                .map(Into::into)+                .unwrap_or_else(|_| atom!("")),+        };+        let extra = Some(StringLiteralExtra {+            raw: raw.clone(),+            raw_value: self.value.clone(),+        });         StringLiteral {             base: ctx.base(self.span),             value: self.value,-            // TODO improve me-            raw: match self.raw {-                Some(value) => value,-                _ => swc_atoms::atom!(""),-            },+            raw,+            extra,         }     } }@@ -73,9 +81,18 @@ impl Babelify for Number {     type Output = NumericLiteral;      fn babelify(self, ctx: &Context) -> Self::Output {+        let raw = match self.raw {+            Some(value) => value,+            None => self.value.to_string().into(),+        };+        let extra = Some(NumericLiteralExtra {+            raw,+            raw_value: self.value,+        });         NumericLiteral {             base: ctx.base(self.span),             value: self.value,+            extra,         }     } }@@ -84,14 +101,20 @@ impl Babelify for BigInt {     type Output = BigIntLiteral;      fn babelify(self, ctx: &Context) -> Self::Output {+        let value = self.value.to_string();+        let raw = match self.raw {+            Some(raw) => raw,+            None => format!("{}n", value).into(),+        };+        let extra = Some(BigIntLiteralExtra {+            raw: raw.clone(),+            raw_value: value.clone(),+        });         BigIntLiteral {             base: ctx.base(self.span),-            value: self.value.to_string(),-            // TODO improve me-            raw: match self.raw {-                Some(value) => value,-                _ => atom!(""),-            },+            value,+            raw,+            extra,         }     } }

deps/swc/crates/swc_estree_compat/src/babelify/module_decl.rs

@@ -371,9 +371,51 @@ impl Babelify for ModuleExportName {     fn babelify(self, ctx: &Context) -> Self::Output {         match self {             ModuleExportName::Ident(ident) => ModuleExportNameType::Ident(ident.babelify(ctx)),-            ModuleExportName::Str(..) => unimplemented!("module string names unimplemented"),+            ModuleExportName::Str(s) => ModuleExportNameType::Ident(swc_estree_ast::Identifier {+                base: ctx.base(s.span),+                name: s.value.to_string_lossy().into(),+                decorators: Default::default(),+                optional: Default::default(),+                type_annotation: Default::default(),+            }),             #[cfg(swc_ast_unknown)]             _ => panic!("unable to access unknown nodes"),         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{sync::Lrc, FileName, SourceMap, DUMMY_SP};+    use swc_ecma_ast::{ModuleExportName, Str};+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::babelify::{Babelify, Context};++    #[test]+    fn test_module_export_string_name() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let ctx = Context {+            fm,+            cm,+            comments: SwcComments::default(),+        };++        let export_name = ModuleExportName::Str(Str {+            span: DUMMY_SP,+            value: atom!("default").into(),+            raw: None,+        });++        let res = export_name.babelify(&ctx);+        match res {+            estree::ModuleExportNameType::Ident(id) => {+                assert_eq!(id.name.as_str(), "default");+            }+            _ => panic!("Expected ModuleExportNameType::Ident"),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/babelify/pat.rs

@@ -200,11 +200,12 @@ impl Babelify for KeyValuePatProp {     type Output = ObjectProperty;      fn babelify(self, ctx: &Context) -> Self::Output {+        let computed = self.key.is_computed();         ObjectProperty {             base: ctx.base(self.span()),             key: self.key.babelify(ctx),             value: self.value.babelify(ctx).into(),-            computed: Default::default(),+            computed,             shorthand: Default::default(),             decorators: Default::default(),         }@@ -244,18 +245,91 @@ impl Babelify for AssignPatProp {     type Output = ObjectProperty;      fn babelify(self, ctx: &Context) -> Self::Output {-        let is_shorthand = self.value.is_none();+        let base = ctx.base(self.span);+        let key_id = self.key.babelify(ctx);+        let mut value_id = key_id.clone();+        value_id.base.leading_comments.clear();+        value_id.base.inner_comments.clear();+        value_id.base.trailing_comments.clear();+        let value = match self.value {+            Some(default_expr) => {+                let right = default_expr.babelify(ctx).into();+                ObjectPropVal::Pattern(PatternLike::AssignmentPat(AssignmentPattern {+                    base: ctx.base(self.span),+                    left: AssignmentPatternLeft::Id(value_id),+                    right: Box::alloc().init(right),+                    decorators: Default::default(),+                    type_annotation: Default::default(),+                }))+            }+            None => ObjectPropVal::Pattern(PatternLike::Id(value_id)),+        };         ObjectProperty {-            base: ctx.base(self.span),-            key: ObjectKey::Id(self.key.clone().babelify(ctx)),-            value: if is_shorthand {-                ObjectPropVal::Pattern(PatternLike::Id(self.key.babelify(ctx)))-            } else {-                ObjectPropVal::Expr(Box::alloc().init(self.value.unwrap().babelify(ctx).into()))-            },-            shorthand: is_shorthand,+            base,+            key: ObjectKey::Id(key_id),+            value,+            shorthand: true,             computed: Default::default(),             decorators: Default::default(),         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{+        comments::{Comment, CommentKind, Comments},+        sync::Lrc,+        BytePos, FileName, SourceMap, Span, SyntaxContext, DUMMY_SP,+    };+    use swc_ecma_ast::{AssignPatProp, BindingIdent, Expr, Ident, Lit, Number};+    use swc_estree_ast::{AssignmentPatternLeft, ObjectPropVal, PatternLike};+    use swc_node_comments::SwcComments;++    use crate::babelify::{Babelify, Context};++    #[test]+    fn test_babelify_assign_pat_prop_with_default() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), "x = 1");+        let comments = SwcComments::default();+        let prop_span = Span::new(BytePos(1), BytePos(6));+        comments.add_leading(+            prop_span.lo,+            Comment {+                kind: CommentKind::Block,+                span: DUMMY_SP,+                text: atom!(" prop comment "),+            },+        );+        let ctx = Context { fm, cm, comments };++        let assign_pat_prop = AssignPatProp {+            span: prop_span,+            key: BindingIdent {+                id: Ident::new(atom!("x"), DUMMY_SP, SyntaxContext::empty()),+                type_ann: None,+            },+            value: Some(Box::new(Expr::Lit(Lit::Num(Number {+                span: DUMMY_SP,+                value: 1.0,+                raw: None,+            })))),+        };++        let obj_prop = assign_pat_prop.babelify(&ctx);+        assert!(obj_prop.shorthand);+        assert_eq!(obj_prop.base.leading_comments.len(), 1);+        match obj_prop.value {+            ObjectPropVal::Pattern(PatternLike::AssignmentPat(assign_pat)) => {+                assert!(assign_pat.base.leading_comments.is_empty());+                match assign_pat.left {+                    AssignmentPatternLeft::Id(id) => assert_eq!(id.name.as_str(), "x"),+                    _ => panic!("Expected AssignmentPatternLeft::Id"),+                }+            }+            _ => panic!("Expected ObjectPropVal::Pattern(AssignmentPat)"),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/babelify/prop.rs

@@ -5,7 +5,7 @@ use swc_ecma_ast::{     SetterProp, }; use swc_estree_ast::{-    AssignmentPattern, AssignmentPatternLeft, Expression, FunctionExpression, ObjectKey,+    AssignmentPattern, AssignmentPatternLeft, Expression, FunctionExpression, Literal, ObjectKey,     ObjectMember, ObjectMethod, ObjectMethodKind, ObjectPropVal, ObjectProperty, }; @@ -17,6 +17,7 @@ fn babelify_object_method(     kind: ObjectMethodKind,     ctx: &Context, ) -> ObjectMethod {+    let computed = key.is_computed();     let func: FunctionExpression = function.babelify(ctx);      ObjectMethod {@@ -26,7 +27,7 @@ fn babelify_object_method(         params: func.params,         body: func.body,         return_type: func.return_type,-        computed: Default::default(),+        computed,         generator: func.generator,         is_async: func.is_async,         decorator: Default::default(),@@ -66,11 +67,12 @@ impl Babelify for KeyValueProp {     type Output = ObjectProperty;      fn babelify(self, ctx: &Context) -> Self::Output {+        let computed = self.key.is_computed();         ObjectProperty {             base: ctx.base(self.span()),             key: self.key.babelify(ctx),             value: ObjectPropVal::Expr(Box::alloc().init(self.value.babelify(ctx).into())),-            computed: Default::default(),+            computed,             shorthand: Default::default(),             decorators: Default::default(),         }@@ -126,6 +128,10 @@ impl Babelify for PropName {             PropName::Str(s) => ObjectKey::String(s.babelify(ctx)),             PropName::Num(n) => ObjectKey::Numeric(n.babelify(ctx)),             PropName::Computed(e) => ObjectKey::Expr(Box::alloc().init(e.babelify(ctx))),+            PropName::BigInt(b) => ObjectKey::Expr(+                Box::alloc().init(Expression::Literal(Literal::BigInt(b.babelify(ctx)))),+            ),+            #[cfg(swc_ast_unknown)]             _ => panic!(                 "illegal conversion: Cannot convert {:?} to ObjectKey",                 &self@@ -141,3 +147,41 @@ impl Babelify for ComputedPropName {         self.expr.babelify(ctx).into()     } }++#[cfg(test)]+mod tests {+    use swc_common::{sync::Lrc, FileName, SourceMap, DUMMY_SP};+    use swc_ecma_ast::{BigInt, PropName};+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::babelify::{Babelify, Context};++    #[test]+    fn test_bigint_prop_name() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let ctx = Context {+            fm,+            cm,+            comments: SwcComments::default(),+        };++        let prop_name = PropName::BigInt(BigInt {+            span: DUMMY_SP,+            value: Box::new(123u64.into()),+            raw: Some("123n".into()),+        });++        let key = prop_name.babelify(&ctx);+        match key {+            estree::ObjectKey::Expr(expr) => match *expr {+                estree::Expression::Literal(estree::Literal::BigInt(bi)) => {+                    assert_eq!(bi.value.as_str(), "123");+                }+                _ => panic!("Expected Literal::BigInt"),+            },+            _ => panic!("Expected ObjectKey::Expr"),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/babelify/stmt.rs

@@ -287,7 +287,7 @@ impl Babelify for ForOfStmt {             left: self.left.babelify(ctx),             right: Box::alloc().init(self.right.babelify(ctx).into()),             body: Box::alloc().init(self.body.babelify(ctx)),-            // await_token not yet implemented+            is_await: self.is_await,         }     } }

deps/swc/crates/swc_estree_compat/src/swcify/class.rs

@@ -49,7 +49,11 @@ impl Swcify for swc_estree_ast::ClassMethod {                  swc_ecma_ast::ClassMethod {                     span: ctx.span(&self.base),-                    key: self.key.swcify(ctx),+                    key: crate::swcify::expr::swcify_object_key(+                        self.key,+                        self.computed.unwrap_or_default(),+                        ctx,+                    ),                     function: Function {                         this_param,                         params,@@ -68,7 +72,7 @@ impl Swcify for swc_estree_ast::ClassMethod {                         .map(|kind| match kind {                             ClassMethodKind::Get => MethodKind::Getter,                             ClassMethodKind::Set => MethodKind::Setter,-                            ClassMethodKind::Method => MethodKind::Getter,+                            ClassMethodKind::Method => MethodKind::Method,                             ClassMethodKind::Constructor => {                                 unreachable!()                             }@@ -84,7 +88,11 @@ impl Swcify for swc_estree_ast::ClassMethod {             }             ClassMethodKind::Constructor => swc_ecma_ast::Constructor {                 span: ctx.span(&self.base),-                key: self.key.swcify(ctx),+                key: crate::swcify::expr::swcify_object_key(+                    self.key,+                    self.computed.unwrap_or_default(),+                    ctx,+                ),                 params: self                     .params                     .into_iter()@@ -127,7 +135,7 @@ impl Swcify for swc_estree_ast::ClassPrivateMethod {             kind: match self.kind.unwrap_or(ClassMethodKind::Method) {                 ClassMethodKind::Get => MethodKind::Getter,                 ClassMethodKind::Set => MethodKind::Setter,-                ClassMethodKind::Method => MethodKind::Getter,+                ClassMethodKind::Method => MethodKind::Method,                 ClassMethodKind::Constructor => {                     unreachable!()                 }@@ -145,7 +153,11 @@ impl Swcify for swc_estree_ast::ClassProperty {     type Output = swc_ecma_ast::ClassProp;      fn swcify(self, ctx: &Context) -> Self::Output {-        let key = self.key.swcify(ctx);+        let key = crate::swcify::expr::swcify_object_key(+            self.key,+            self.computed.unwrap_or_default(),+            ctx,+        );          swc_ecma_ast::ClassProp {             span: ctx.span(&self.base),@@ -174,7 +186,7 @@ impl Swcify for swc_estree_ast::ClassPrivateProperty {             key: self.key.swcify(ctx),             value: self.value.swcify(ctx),             type_ann: self.type_annotation.swcify(ctx).flatten().map(Box::new),-            is_static: false,+            is_static: self.static_any.as_bool().unwrap_or(false),             decorators: Default::default(),             accessibility: Default::default(),             is_optional: false,@@ -229,3 +241,111 @@ impl Swcify for TSExpressionWithTypeArguments {         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{sync::Lrc, FileName, SourceMap};+    use swc_ecma_ast::{ClassMember, MethodKind};+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::swcify::{Context, Swcify};++    fn base() -> estree::BaseNode {+        estree::BaseNode {+            leading_comments: Default::default(),+            inner_comments: Default::default(),+            trailing_comments: Default::default(),+            start: None,+            end: None,+            loc: None,+            range: None,+        }+    }++    #[test]+    fn test_swcify_class_method_kind_method() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let comments = SwcComments::default();+        let ctx = Context::new_without_alloc(cm, comments, fm);++        let method = estree::ClassMethod {+            base: base(),+            kind: Some(estree::ClassMethodKind::Method),+            key: estree::ObjectKey::Id(estree::Identifier {+                base: base(),+                name: atom!("foo"),+                type_annotation: None,+                optional: None,+                decorators: None,+            }),+            params: vec![],+            body: estree::BlockStatement {+                base: base(),+                body: vec![],+                directives: vec![],+            },+            computed: None,+            is_static: None,+            generator: None,+            is_async: None,+            is_abstract: None,+            access: None,+            accessibility: None,+            decorators: None,+            optional: None,+            return_type: None,+            type_parameters: None,+        };++        let class_member: ClassMember = method.swcify(&ctx);+        match class_member {+            ClassMember::Method(m) => {+                assert_eq!(m.kind, MethodKind::Method);+            }+            _ => panic!("Expected ClassMember::Method"),+        }++        let private_method = estree::ClassBodyEl::PrivateMethod(estree::ClassPrivateMethod {+            base: base(),+            kind: Some(estree::ClassMethodKind::Method),+            key: estree::PrivateName {+                base: base(),+                id: estree::Identifier {+                    base: base(),+                    name: atom!("bar"),+                    type_annotation: None,+                    optional: None,+                    decorators: None,+                },+            },+            params: vec![],+            body: estree::BlockStatement {+                base: base(),+                body: vec![],+                directives: vec![],+            },+            is_static: None,+            generator: None,+            is_async: None,+            is_abstract: None,+            access: None,+            accessibility: None,+            decorators: None,+            optional: None,+            computed: None,+            return_type: None,+            type_parameters: None,+        });++        let private_member: ClassMember = private_method.swcify(&ctx);+        match private_member {+            ClassMember::PrivateMethod(m) => {+                assert_eq!(m.kind, MethodKind::Method);+            }+            _ => panic!("Expected ClassMember::PrivateMethod"),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/swcify/expr.rs

@@ -379,6 +379,13 @@ impl Swcify for MemberExpression {                 obj: s.swcify(ctx),                 prop: match (*self.property, self.computed) {                     (MemberExprProp::Id(i), false) => SuperProp::Ident(i.swcify(ctx).into()),+                    (MemberExprProp::Id(i), true) => {+                        let expr: Box<Expr> = Box::new(Expr::Ident(i.swcify(ctx).id));+                        SuperProp::Computed(ComputedPropName {+                            span: expr.span(),+                            expr,+                        })+                    }                     (MemberExprProp::Expr(e), true) => {                         let expr = e.swcify(ctx);                         SuperProp::Computed(ComputedPropName {@@ -395,6 +402,13 @@ impl Swcify for MemberExpression {                 obj: self.object.swcify(ctx),                 prop: match (*self.property, self.computed) {                     (MemberExprProp::Id(i), false) => MemberProp::Ident(i.swcify(ctx).into()),+                    (MemberExprProp::Id(i), true) => {+                        let expr: Box<Expr> = Box::new(Expr::Ident(i.swcify(ctx).id));+                        MemberProp::Computed(ComputedPropName {+                            span: expr.span(),+                            expr,+                        })+                    }                     (MemberExprProp::PrivateName(e), false) => {                         MemberProp::PrivateName(e.swcify(ctx))                     }@@ -454,8 +468,41 @@ impl Swcify for ObjectExprProp {      fn swcify(self, ctx: &Context) -> Self::Output {         match self {-            ObjectExprProp::Method(m) => PropOrSpread::Prop(Box::new(Prop::Method(m.swcify(ctx)))),-            ObjectExprProp::Prop(p) => PropOrSpread::Prop(Box::new(Prop::KeyValue(p.swcify(ctx)))),+            ObjectExprProp::Method(m) => {+                let kind = m.kind;+                let method = m.swcify(ctx);+                match kind {+                    swc_estree_ast::ObjectMethodKind::Get => {+                        PropOrSpread::Prop(Box::new(Prop::Getter(swc_ecma_ast::GetterProp {+                            span: method.function.span,+                            key: method.key,+                            function: method.function,+                        })))+                    }+                    swc_estree_ast::ObjectMethodKind::Set => {+                        PropOrSpread::Prop(Box::new(Prop::Setter(swc_ecma_ast::SetterProp {+                            span: method.function.span,+                            key: method.key,+                            function: method.function,+                        })))+                    }+                    swc_estree_ast::ObjectMethodKind::Method => {+                        PropOrSpread::Prop(Box::new(Prop::Method(method)))+                    }+                }+            }+            ObjectExprProp::Prop(p) => {+                if let (true, false, ObjectKey::Id(key_id), ObjectPropVal::Expr(val_expr)) =+                    (p.shorthand, p.computed, &p.key, &p.value)+                {+                    if matches!(&**val_expr, Expression::Id(val_id) if key_id.name == val_id.name) {+                        return PropOrSpread::Prop(Box::new(Prop::Shorthand(+                            key_id.clone().swcify(ctx).id,+                        )));+                    }+                }+                PropOrSpread::Prop(Box::new(Prop::KeyValue(p.swcify(ctx))))+            }             ObjectExprProp::Spread(p) => PropOrSpread::Spread(SpreadElement {                 // TODO: Use exact span                 dot3_token: ctx.span(&p.base),@@ -473,7 +520,7 @@ impl Swcify for ObjectMethod {             swcify_function_params(self.params, ctx);          MethodProp {-            key: self.key.swcify(ctx),+            key: swcify_object_key(self.key, self.computed, ctx),             function: Box::new(Function {                 this_param,                 params,@@ -509,23 +556,40 @@ impl Swcify for ObjectKey {             ObjectKey::Id(v) => PropName::Ident(v.swcify(ctx).into()),             ObjectKey::String(v) => PropName::Str(v.swcify(ctx)),             ObjectKey::Numeric(v) => PropName::Num(v.swcify(ctx)),-            ObjectKey::Expr(v) => {-                let expr = v.swcify(ctx);-                PropName::Computed(ComputedPropName {+            ObjectKey::Expr(v) => match *v.swcify(ctx) {+                Expr::Lit(Lit::BigInt(b)) => PropName::BigInt(b),+                expr => PropName::Computed(ComputedPropName {                     span: expr.span(),-                    expr,-                })-            }+                    expr: Box::new(expr),+                }),+            },         }     } } +pub(crate) fn swcify_object_key(key: ObjectKey, computed: bool, ctx: &Context) -> PropName {+    if computed {+        let expr: Box<Expr> = match key {+            ObjectKey::Id(v) => Box::new(v.swcify(ctx).id.into()),+            ObjectKey::String(v) => Box::new(Lit::Str(v.swcify(ctx)).into()),+            ObjectKey::Numeric(v) => Box::new(Lit::Num(v.swcify(ctx)).into()),+            ObjectKey::Expr(v) => v.swcify(ctx),+        };+        PropName::Computed(ComputedPropName {+            span: expr.span(),+            expr,+        })+    } else {+        key.swcify(ctx)+    }+}+ impl Swcify for ObjectProperty {     type Output = KeyValueProp;      fn swcify(self, ctx: &Context) -> Self::Output {         KeyValueProp {-            key: self.key.swcify(ctx),+            key: swcify_object_key(self.key, self.computed, ctx),             value: match self.value {                 ObjectPropVal::Pattern(pat) => match pat {                     PatternLike::Id(i) => i.swcify(ctx).into(),@@ -792,6 +856,13 @@ impl Swcify for OptionalMemberExpression {                     (OptionalMemberExprProp::Id(i), false) => {                         MemberProp::Ident(i.swcify(ctx).into())                     }+                    (OptionalMemberExprProp::Id(i), true) => {+                        let expr: Box<Expr> = Box::new(Expr::Ident(i.swcify(ctx).id));+                        MemberProp::Computed(ComputedPropName {+                            span: expr.span(),+                            expr,+                        })+                    }                     (OptionalMemberExprProp::Expr(e), true) => {                         let expr = e.swcify(ctx);                         MemberProp::Computed(ComputedPropName {@@ -1199,3 +1270,129 @@ impl Swcify for ArrayExprEl {         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{sync::Lrc, FileName, SourceMap};+    use swc_ecma_ast::{Expr, MemberProp, OptChainBase, SuperProp};+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::swcify::{Context, Swcify};++    fn base() -> estree::BaseNode {+        estree::BaseNode {+            leading_comments: Default::default(),+            inner_comments: Default::default(),+            trailing_comments: Default::default(),+            start: None,+            end: None,+            loc: None,+            range: None,+        }+    }++    #[test]+    fn test_swcify_computed_member_identifier() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let comments = SwcComments::default();+        let ctx = Context::new_without_alloc(cm, comments, fm);++        let member_expr = estree::MemberExpression {+            base: base(),+            object: Box::new(estree::Expression::Id(estree::Identifier {+                base: base(),+                name: atom!("arr"),+                type_annotation: None,+                optional: None,+                decorators: None,+            })),+            property: Box::new(estree::MemberExprProp::Id(estree::Identifier {+                base: base(),+                name: atom!("i"),+                type_annotation: None,+                optional: None,+                decorators: None,+            })),+            computed: true,+            optional: None,+        };++        let swc_expr = member_expr.swcify(&ctx);+        assert!(matches!(+            swc_expr,+            Expr::Member(m) if matches!(m.prop, MemberProp::Computed(_))+        ));++        let super_member_expr = estree::MemberExpression {+            base: base(),+            object: Box::new(estree::Expression::Super(estree::Super { base: base() })),+            property: Box::new(estree::MemberExprProp::Id(estree::Identifier {+                base: base(),+                name: atom!("i"),+                type_annotation: None,+                optional: None,+                decorators: None,+            })),+            computed: true,+            optional: None,+        };++        let swc_super_expr = super_member_expr.swcify(&ctx);+        assert!(matches!(+            swc_super_expr,+            Expr::SuperProp(s) if matches!(s.prop, SuperProp::Computed(_))+        ));++        let opt_member_expr = estree::OptionalMemberExpression {+            base: base(),+            object: Box::new(estree::Expression::Id(estree::Identifier {+                base: base(),+                name: atom!("arr"),+                type_annotation: None,+                optional: None,+                decorators: None,+            })),+            property: estree::OptionalMemberExprProp::Id(estree::Identifier {+                base: base(),+                name: atom!("i"),+                type_annotation: None,+                optional: None,+                decorators: None,+            }),+            computed: true,+            optional: true,+        };++        let swc_opt_expr = opt_member_expr.swcify(&ctx);+        assert!(matches!(+            *swc_opt_expr.base,+            OptChainBase::Member(m) if matches!(m.prop, MemberProp::Computed(_))+        ));+    }++    #[test]+    fn test_swcify_non_computed_bigint_object_key() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let comments = SwcComments::default();+        let ctx = Context::new_without_alloc(cm, comments, fm);++        let key = estree::ObjectKey::Expr(Box::new(estree::Expression::Literal(+            estree::Literal::BigInt(estree::BigIntLiteral {+                base: base(),+                value: "10".to_string(),+                raw: atom!("10n"),+                extra: None,+            }),+        )));++        let non_computed = super::swcify_object_key(key.clone(), false, &ctx);+        assert!(matches!(non_computed, swc_ecma_ast::PropName::BigInt(_)));++        let computed = super::swcify_object_key(key, true, &ctx);+        assert!(matches!(computed, swc_ecma_ast::PropName::Computed(_)));+    }+}

deps/swc/crates/swc_estree_compat/src/swcify/lit.rs

@@ -29,10 +29,14 @@ impl Swcify for StringLiteral {     type Output = Str;      fn swcify(self, ctx: &Context) -> Self::Output {+        let raw = self+            .extra+            .and_then(|extra| (!extra.raw.is_empty()).then_some(extra.raw))+            .or_else(|| (!self.raw.is_empty()).then_some(self.raw));         Str {             span: ctx.span(&self.base),             value: self.value,-            raw: Some(self.raw),+            raw,         }     } }@@ -54,11 +58,13 @@ impl Swcify for NumericLiteral {     type Output = Number;      fn swcify(self, ctx: &Context) -> Self::Output {+        let raw = self+            .extra+            .and_then(|extra| (!extra.raw.is_empty()).then_some(extra.raw));         Number {             span: ctx.span(&self.base),             value: self.value,-            // TODO improve me-            raw: None,+            raw,         }     } }@@ -136,15 +142,13 @@ impl Swcify for BigIntLiteral {     type Output = BigInt;      fn swcify(self, ctx: &Context) -> Self::Output {+        let raw = self+            .extra+            .and_then(|extra| (!extra.raw.is_empty()).then_some(extra.raw));         BigInt {             span: ctx.span(&self.base),-            value: self-                .value-                .parse()-                .map(Box::new)-                .expect("failed to parse the value of BigIntLiteral"),-            // TODO improve me-            raw: None,+            value: Box::new(self.value.trim_end_matches('n').parse().unwrap_or_default()),+            raw,         }     } }

deps/swc/crates/swc_estree_compat/src/swcify/pat.rs

@@ -110,26 +110,55 @@ impl Swcify for ObjectPatternProp {         match self {             ObjectPatternProp::Rest(v) => ObjectPatProp::Rest(v.swcify(ctx)),             ObjectPatternProp::Prop(prop) => {-                if prop.shorthand {-                    return ObjectPatProp::Assign(AssignPatProp {+                match (prop.shorthand && !prop.computed, prop.key, prop.value) {+                    (+                        true,+                        swc_estree_ast::ObjectKey::Id(key_id),+                        swc_estree_ast::ObjectPropVal::Pattern(swc_estree_ast::PatternLike::Id(+                            left_id,+                        )),+                    ) if key_id.name == left_id.name => ObjectPatProp::Assign(AssignPatProp {                         span: ctx.span(&prop.base),-                        key: prop.key.swcify(ctx).expect_ident().into(),+                        key: key_id.swcify(ctx),                         value: None,-                    });-                }--                match prop.value {-                    swc_estree_ast::ObjectPropVal::Pattern(v) => {+                    }),+                    (+                        true,+                        swc_estree_ast::ObjectKey::Id(key_id),+                        swc_estree_ast::ObjectPropVal::Pattern(+                            swc_estree_ast::PatternLike::AssignmentPat(a),+                        ),+                    ) if matches!(+                        &a.left,+                        swc_estree_ast::AssignmentPatternLeft::Id(left_id)+                            if key_id.name == left_id.name+                    ) =>+                    {+                        ObjectPatProp::Assign(AssignPatProp {+                            span: ctx.span(&prop.base),+                            key: key_id.swcify(ctx),+                            value: Some(a.right.swcify(ctx)),+                        })+                    }+                    (_, key, swc_estree_ast::ObjectPropVal::Pattern(v)) => {                         ObjectPatProp::KeyValue(KeyValuePatProp {-                            key: prop.key.swcify(ctx),+                            key: crate::swcify::expr::swcify_object_key(key, prop.computed, ctx),                             value: Box::new(v.swcify(ctx)),                         })                     }-                    swc_estree_ast::ObjectPropVal::Expr(v) => {-                        ObjectPatProp::Assign(AssignPatProp {-                            span: ctx.span(&prop.base),-                            key: prop.key.swcify(ctx).expect_ident().into(),-                            value: Some(v.swcify(ctx)),+                    (+                        _,+                        swc_estree_ast::ObjectKey::Id(key_id),+                        swc_estree_ast::ObjectPropVal::Expr(v),+                    ) => ObjectPatProp::Assign(AssignPatProp {+                        span: ctx.span(&prop.base),+                        key: key_id.swcify(ctx),+                        value: Some(v.swcify(ctx)),+                    }),+                    (_, key, swc_estree_ast::ObjectPropVal::Expr(v)) => {+                        ObjectPatProp::KeyValue(KeyValuePatProp {+                            key: key.swcify(ctx),+                            value: Box::new(Pat::Expr(v.swcify(ctx))),                         })                     }                 }@@ -182,3 +211,102 @@ impl Swcify for swc_estree_ast::Param {         }     } }++#[cfg(test)]+mod tests {+    use swc_atoms::atom;+    use swc_common::{sync::Lrc, FileName, SourceMap};+    use swc_ecma_ast::ObjectPatProp;+    use swc_estree_ast as estree;+    use swc_node_comments::SwcComments;++    use crate::swcify::{Context, Swcify};++    fn base() -> estree::BaseNode {+        estree::BaseNode {+            leading_comments: Default::default(),+            inner_comments: Default::default(),+            trailing_comments: Default::default(),+            start: None,+            end: None,+            loc: None,+            range: None,+        }+    }++    #[test]+    fn test_swcify_object_pattern_shorthand_assignment_pat() {+        let cm = Lrc::new(SourceMap::default());+        let fm = cm.new_source_file(Lrc::new(FileName::Anon), String::new());+        let comments = SwcComments::default();+        let ctx = Context::new_without_alloc(cm, comments, fm);++        let prop = estree::ObjectPatternProp::Prop(estree::ObjectProperty {+            base: base(),+            key: estree::ObjectKey::Id(estree::Identifier {+                base: base(),+                name: atom!("x"),+                type_annotation: None,+                optional: None,+                decorators: None,+            }),+            value: estree::ObjectPropVal::Pattern(estree::PatternLike::AssignmentPat(+                estree::AssignmentPattern {+                    base: base(),+                    left: estree::AssignmentPatternLeft::Id(estree::Identifier {+                        base: base(),+                        name: atom!("x"),+                        type_annotation: None,+                        optional: None,+                        decorators: None,+                    }),+                    right: Box::new(estree::Expression::Literal(estree::Literal::Numeric(+                        estree::NumericLiteral {+                            base: base(),+                            value: 1.0,+                            extra: None,+                        },+                    ))),+                    type_annotation: None,+                    decorators: None,+                },+            )),+            computed: false,+            shorthand: true,+            decorators: None,+        });++        let pat_prop: ObjectPatProp = prop.swcify(&ctx);+        match pat_prop {+            ObjectPatProp::Assign(assign_prop) => {+                assert_eq!(assign_prop.key.id.sym.as_str(), "x");+                assert!(assign_prop.value.is_some());+            }+            _ => panic!("Expected ObjectPatProp::Assign"),+        }++        let mismatched_shorthand = estree::ObjectPatternProp::Prop(estree::ObjectProperty {+            base: base(),+            key: estree::ObjectKey::Id(estree::Identifier {+                base: base(),+                name: atom!("x"),+                type_annotation: None,+                optional: None,+                decorators: None,+            }),+            value: estree::ObjectPropVal::Pattern(estree::PatternLike::Id(estree::Identifier {+                base: base(),+                name: atom!("y"),+                type_annotation: None,+                optional: None,+                decorators: None,+            })),+            computed: false,+            shorthand: true,+            decorators: None,+        });++        let mismatched_pat_prop: ObjectPatProp = mismatched_shorthand.swcify(&ctx);+        assert!(matches!(mismatched_pat_prop, ObjectPatProp::KeyValue(_)));+    }+}

deps/swc/crates/swc_estree_compat/src/swcify/program.rs

@@ -1 +1,48 @@+use swc_ecma_ast::{Module, ModuleItem, Program, Script};+use swc_estree_ast::{File, Program as BabelProgram, SrcType}; +use crate::swcify::{Context, Swcify};++impl Swcify for File {+    type Output = Program;++    fn swcify(self, ctx: &Context) -> Self::Output {+        self.program.swcify(ctx)+    }+}++impl Swcify for BabelProgram {+    type Output = Program;++    fn swcify(self, ctx: &Context) -> Self::Output {+        let span = ctx.span(&self.base);+        let shebang = self.interpreter.map(|i| i.value);+        let body: Vec<ModuleItem> = self.body.swcify(ctx);+        let has_module_decl = body+            .iter()+            .any(|item| matches!(item, ModuleItem::ModuleDecl(_)));+        match self.source_type {+            SrcType::Module => Program::Module(Module {+                span,+                body,+                shebang,+            }),+            SrcType::Script if has_module_decl => Program::Module(Module {+                span,+                body,+                shebang,+            }),+            SrcType::Script => Program::Script(Script {+                span,+                body: body+                    .into_iter()+                    .filter_map(|item| match item {+                        ModuleItem::Stmt(stmt) => Some(stmt),+                        ModuleItem::ModuleDecl(_) => None,+                    })+                    .collect(),+                shebang,+            }),+        }+    }+}

deps/swc/crates/swc_estree_compat/src/swcify/stmt.rs

@@ -609,7 +609,7 @@ impl Swcify for ForOfStatement {     fn swcify(self, ctx: &Context) -> Self::Output {         ForOfStmt {             span: ctx.span(&self.base),-            is_await: false,+            is_await: self.is_await,             left: self.left.swcify(ctx),             right: self.right.swcify(ctx),             body: Box::new(self.body.swcify(ctx).expect_stmt()),

deps/swc/crates/swc_html/Cargo.toml

@@ -24,5 +24,5 @@ minifier = [] [dependencies] swc_html_ast     = { version = "26.0.0", path = "../swc_html_ast" } swc_html_codegen = { version = "27.0.0", path = "../swc_html_codegen" }-swc_html_parser  = { version = "26.0.0", path = "../swc_html_parser" }+swc_html_parser  = { version = "26.1.0", path = "../swc_html_parser" } swc_html_visit   = { version = "26.0.0", path = "../swc_html_visit" }

deps/swc/crates/swc_html_codegen/Cargo.toml

@@ -30,6 +30,6 @@ swc_html_utils          = { version = "17.0.0", path = "../swc_html_utils" } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap", ] }-swc_html_parser = { version = "26.0.0", path = "../swc_html_parser" }+swc_html_parser = { version = "26.1.0", path = "../swc_html_parser" } swc_html_visit = { version = "26.0.0", path = "../swc_html_visit" } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_html_minifier/Cargo.toml

@@ -10,7 +10,7 @@ include = ["Cargo.toml", "src/**/*.rs", "data/**/*.json"] license = { workspace = true } name = "swc_html_minifier" repository = "https://github.com/swc-project/swc.git"-version = "61.0.0"+version = "63.0.0"  [lib] bench = false@@ -36,26 +36,26 @@ serde_json = { workspace = true }  swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common" }-swc_config = { version = "6.0.0", path = "../swc_config", features = ["regex"] }+swc_config = { version = "6.0.1", path = "../swc_config", features = ["regex"] } swc_css_ast = { version = "26.0.0", path = "../swc_css_ast", optional = true } swc_css_codegen = { version = "26.0.0", path = "../swc_css_codegen", optional = true } swc_css_minifier = { version = "26.0.0", path = "../swc_css_minifier", optional = true } swc_css_parser = { version = "26.0.0", path = "../swc_css_parser", optional = true }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen", features = [   "serde-impl", ] }-swc_ecma_minifier = { version = "61.0.5", path = "../swc_ecma_minifier", features = [+swc_ecma_minifier = { version = "63.0.1", path = "../swc_ecma_minifier", features = [   "extra-serde", ] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_html_ast = { version = "26.0.0", path = "../swc_html_ast" } swc_html_codegen = { version = "27.0.0", path = "../swc_html_codegen" }-swc_html_parser = { version = "26.0.0", path = "../swc_html_parser" }+swc_html_parser = { version = "26.1.0", path = "../swc_html_parser" } swc_html_utils = { version = "17.0.0", path = "../swc_html_utils" } swc_html_visit = { version = "26.0.0", path = "../swc_html_visit" } 

deps/swc/crates/swc_html_minifier/src/lib.rs

@@ -1203,18 +1203,16 @@ impl<C: MinifyCss> Minifier<'_, C> {         true     } -    fn allow_elements_to_merge(&self, left: Option<&Child>, right: &Element) -> bool {+    fn allow_styles_to_merge(&self, left: Option<&Child>, right: &Element) -> bool {         if let Some(Child::Element(left)) = left {+            // Separate script elements have separate evaluation contexts and DOM+            // identities.             let is_style_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)                 && left.tag_name == "style"                 && matches!(right.namespace, Namespace::HTML | Namespace::SVG)                 && right.tag_name == "style";-            let is_script_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)-                && left.tag_name == "script"-                && matches!(right.namespace, Namespace::HTML | Namespace::SVG)-                && right.tag_name == "script"; -            if is_style_tag || is_script_tag {+            if is_style_tag {                 let mut need_skip = false;                  let mut left_attributes = left@@ -1222,21 +1220,10 @@ impl<C: MinifyCss> Minifier<'_, C> {                     .clone()                     .into_iter()                     .filter(|attribute| match &*attribute.name {-                        "src" if is_script_tag => {-                            need_skip = true;--                            true-                        }                         "type" => {                             if let Some(value) = &attribute.value {-                                if (is_style_tag && self.is_type_text_css(value))-                                    || (is_script_tag && self.is_type_text_javascript(value))-                                {+                                if self.is_type_text_css(value) {                                     false-                                } else if is_script_tag-                                    && value.trim().eq_ignore_ascii_case("module")-                                {-                                    true                                 } else {                                     need_skip = true; @@ -1264,21 +1251,10 @@ impl<C: MinifyCss> Minifier<'_, C> {                     .clone()                     .into_iter()                     .filter(|attribute| match &*attribute.name {-                        "src" if is_script_tag => {-                            need_skip = true;--                            true-                        }                         "type" => {                             if let Some(value) = &attribute.value {-                                if (is_style_tag && self.is_type_text_css(value))-                                    || (is_script_tag && self.is_type_text_javascript(value))-                                {+                                if self.is_type_text_css(value) {                                     false-                                } else if is_script_tag-                                    && value.trim().eq_ignore_ascii_case("module")-                                {-                                    true                                 } else {                                     need_skip = true; @@ -1311,46 +1287,23 @@ impl<C: MinifyCss> Minifier<'_, C> {         false     } -    fn merge_text_children(&self, left: &Element, right: &Element) -> Option<Vec<Child>> {-        let is_script_tag = matches!(left.namespace, Namespace::HTML | Namespace::SVG)-            && left.tag_name == "script"-            && matches!(right.namespace, Namespace::HTML | Namespace::SVG)-            && right.tag_name == "script";--        // `script`/`style` elements should have only one text child+    fn merge_style_text_children(left: &Element, right: &Element) -> Option<Vec<Child>> {+        // `style` elements should have only one text child.         let left_data = match left.children.first() {-            Some(Child::Text(left)) => left.data.to_string(),-            None => String::new(),+            Some(Child::Text(left)) => &*left.data,+            None => "",             _ => return None,         };          let right_data = match right.children.first() {-            Some(Child::Text(right)) => right.data.to_string(),-            None => String::new(),+            Some(Child::Text(right)) => &*right.data,+            None => "",             _ => return None,         };          let mut data = String::with_capacity(left_data.len() + right_data.len());--        if is_script_tag {-            let is_modules = if is_script_tag {-                left.attributes.iter().any(|attribute| matches!(&attribute.value, Some(value) if value.trim().eq_ignore_ascii_case("module")))-            } else {-                false-            };--            match self.merge_js(left_data, right_data, is_modules) {-                Some(minified) => {-                    data.push_str(&minified);-                }-                _ => {-                    return None;-                }-            }-        } else {-            data.push_str(&left_data);-            data.push_str(&right_data);-        }+        data.push_str(left_data);+        data.push_str(right_data);          if data.is_empty() {             return Some(Vec::new());@@ -1383,10 +1336,10 @@ impl<C: MinifyCss> Minifier<'_, C> {                     }                     Child::Element(element)                         if self.options.merge_metadata_elements-                            && self.allow_elements_to_merge(prev_children.last(), element) =>+                            && self.allow_styles_to_merge(prev_children.last(), element) =>                     {                         if let Some(Child::Element(prev)) = prev_children.last_mut() {-                            if let Some(children) = self.merge_text_children(prev, element) {+                            if let Some(children) = Self::merge_style_text_children(prev, element) {                                 prev.children = children;                                  false@@ -1825,154 +1778,6 @@ impl<C: MinifyCss> Minifier<'_, C> {         }     } -    fn merge_js(&self, left: String, right: String, is_modules: bool) -> Option<String> {-        let comments = SingleThreadedComments::default();-        let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));--        // Left-        let mut left_errors: Vec<_> = Vec::new();-        let left_fm = cm.new_source_file(FileName::Anon.into(), left);-        let syntax = swc_ecma_parser::Syntax::default();-        // Use the latest target for merging-        let target = swc_ecma_ast::EsVersion::latest();--        let mut left_program = if is_modules {-            match swc_ecma_parser::parse_file_as_module(-                &left_fm,-                syntax,-                target,-                Some(&comments),-                &mut left_errors,-            ) {-                Ok(module) => swc_ecma_ast::Program::Module(module),-                _ => return None,-            }-        } else {-            match swc_ecma_parser::parse_file_as_script(-                &left_fm,-                syntax,-                target,-                Some(&comments),-                &mut left_errors,-            ) {-                Ok(script) => swc_ecma_ast::Program::Script(script),-                _ => return None,-            }-        };--        // Avoid compress potential invalid JS-        if !left_errors.is_empty() {-            return None;-        }--        let unresolved_mark = Mark::new();-        let left_top_level_mark = Mark::new();--        swc_ecma_visit::VisitMutWith::visit_mut_with(-            &mut left_program,-            &mut swc_ecma_transforms_base::resolver(unresolved_mark, left_top_level_mark, false),-        );--        // Right-        let mut right_errors: Vec<_> = Vec::new();-        let right_fm = cm.new_source_file(FileName::Anon.into(), right);--        let mut right_program = if is_modules {-            match swc_ecma_parser::parse_file_as_module(-                &right_fm,-                syntax,-                target,-                Some(&comments),-                &mut right_errors,-            ) {-                Ok(module) => swc_ecma_ast::Program::Module(module),-                _ => return None,-            }-        } else {-            match swc_ecma_parser::parse_file_as_script(-                &right_fm,-                syntax,-                target,-                Some(&comments),-                &mut right_errors,-            ) {-                Ok(script) => swc_ecma_ast::Program::Script(script),-                _ => return None,-            }-        };--        // Avoid compress potential invalid JS-        if !right_errors.is_empty() {-            return None;-        }--        let right_top_level_mark = Mark::new();--        swc_ecma_visit::VisitMutWith::visit_mut_with(-            &mut right_program,-            &mut swc_ecma_transforms_base::resolver(unresolved_mark, right_top_level_mark, false),-        );--        // Merge-        match &mut left_program {-            swc_ecma_ast::Program::Module(left_program) => match right_program {-                swc_ecma_ast::Program::Module(right_program) => {-                    left_program.body.extend(right_program.body);-                }-                _ => {-                    unreachable!();-                }-            },-            swc_ecma_ast::Program::Script(left_program) => match right_program {-                swc_ecma_ast::Program::Script(right_program) => {-                    left_program.body.extend(right_program.body);-                }-                _ => {-                    unreachable!();-                }-            },-            #[cfg(swc_ast_unknown)]-            _ => panic!("unable to access unknown nodes"),-        }--        if is_modules {-            swc_ecma_visit::VisitMutWith::visit_mut_with(-                &mut left_program,-                &mut swc_ecma_transforms_base::hygiene::hygiene(),-            );-        }--        let left_program =-            left_program.apply(swc_ecma_transforms_base::fixer::fixer(Some(&comments)));--        let mut buf = Vec::new();--        {-            let wr = Box::new(swc_ecma_codegen::text_writer::JsWriter::new(-                cm.clone(),-                "\n",-                &mut buf,-                None,-            )) as Box<dyn swc_ecma_codegen::text_writer::WriteJs>;--            let mut emitter = swc_ecma_codegen::Emitter {-                cfg: swc_ecma_codegen::Config::default().with_target(target),-                cm,-                comments: Some(&comments),-                wr,-            };--            emitter.emit_program(&left_program).unwrap();-        }--        let code = match String::from_utf8(buf) {-            Ok(minified) => minified,-            _ => return None,-        };--        Some(code)-    }-     // TODO source map url output for JS and CSS?     fn minify_js(&self, data: String, is_module: bool, is_attribute: bool) -> Option<String> {         let mut errors: Vec<_> = Vec::new();

deps/swc/crates/swc_html_minifier/src/option.rs

@@ -160,8 +160,8 @@ pub struct MinifyOptions<CO> {     pub remove_redundant_attributes: RemoveRedundantAttributes,     #[serde(default = "true_by_default")]     pub collapse_boolean_attributes: bool,-    /// Merge the same metadata elements into one (for example, consecutive-    /// `style` elements will be merged into one `style` element)+    /// Merge consecutive compatible `style` elements into one. Script elements+    /// remain separate because each one is evaluated independently.     #[serde(default = "true_by_default")]     pub merge_metadata_elements: bool,     /// Remove extra whitespace in space and comma separated attribute values

deps/swc/crates/swc_html_parser/Cargo.toml

@@ -10,7 +10,7 @@ include = ["Cargo.toml", "src/**/*.rs", "src/**/*.json"] license = { workspace = true } name = "swc_html_parser" repository = "https://github.com/swc-project/swc.git"-version = "26.0.0"+version = "26.1.0"  [lib] bench = false

deps/swc/crates/swc_html_parser/src/parser/mod.rs

@@ -3852,6 +3852,9 @@ where                     // If the stack of open elements has a p element in button scope, then close a p                     // element.                     //+                    // If a select element is in scope, generate implied end tags. If an option or+                    // optgroup remains in scope, this is a parse error.+                    //                     // Insert an HTML element for the token. Immediately pop the current node off                     // the stack of open elements.                     //@@ -3869,6 +3872,19 @@ where                             self.close_p_element(token_and_info, false);                         } +                        if self.open_elements_stack.has_in_scope("select") {+                            self.open_elements_stack.generate_implied_end_tags();++                            if self.open_elements_stack.has_in_scope("option")+                                || self.open_elements_stack.has_in_scope("optgroup")+                            {+                                self.errors.push(Error::new(+                                    token_and_info.span,+                                    ErrorKind::UnclosedElementsOnStack,+                                ));+                            }+                        }+                         self.insert_html_element(token_and_info)?;                         self.open_elements_stack.pop(); @@ -5745,7 +5761,8 @@ where                     // current node from the stack of open elements.                     //                     // If the current node is an optgroup element, then pop that node from the stack-                    // of open elements. Otherwise, this is a parse error; ignore the token.+                    // of open elements. Otherwise, process the token using the rules for the "in+                    // body" insertion mode, so that an optgroup can be closed through rich content.                     Token::EndTag { tag_name, .. } if tag_name == "optgroup" => {                         match self.open_elements_stack.items.last() {                             Some(node) if is_html_element!(node, "option") => {@@ -5778,16 +5795,19 @@ where                                  self.update_end_tag_span(popped.as_ref(), token_and_info.span);                             }-                            _ => self.errors.push(Error::new(-                                token_and_info.span,-                                ErrorKind::StrayEndTag(tag_name.clone()),-                            )),+                            _ => {+                                self.process_token_using_rules(+                                    token_and_info,+                                    InsertionMode::InBody,+                                )?;+                            }                         }                     }                     // An end tag whose tag name is "option"                     //                     // If the current node is an option element, then pop that node from the stack-                    // of open elements. Otherwise, this is a parse error; ignore the token.+                    // of open elements. Otherwise, process the token using the rules for the "in+                    // body" insertion mode, so that an option can be closed through rich content.                     Token::EndTag { tag_name, .. } if tag_name == "option" => {                         match self.open_elements_stack.items.last() {                             Some(node) if is_html_element!(node, "option") => {@@ -5795,10 +5815,12 @@ where                                  self.update_end_tag_span(popped.as_ref(), token_and_info.span);                             }-                            _ => self.errors.push(Error::new(-                                token_and_info.span,-                                ErrorKind::StrayEndTag(tag_name.clone()),-                            )),+                            _ => {+                                self.process_token_using_rules(+                                    token_and_info,+                                    InsertionMode::InBody,+                                )?;+                            }                         }                     }                     // An end tag whose tag name is "select"@@ -5856,7 +5878,7 @@ where                             .pop_until_tag_name_popped(&["select"]);                         self.reset_insertion_mode();                     }-                    // A start tag whose tag name is one of: "input", "keygen", "textarea"+                    // A start tag whose tag name is "input"                     //                     // Parse error.                     //@@ -5871,9 +5893,7 @@ where                     // Reset the insertion mode appropriately.                     //                     // Reprocess the token.-                    Token::StartTag { tag_name, .. }-                        if matches!(&**tag_name, "input" | "keygen" | "textarea") =>-                    {+                    Token::StartTag { tag_name, .. } if tag_name == "input" => {                         self.errors.push(Error::new(                             token_and_info.span,                             ErrorKind::StartTagWithSelectOpen(tag_name.clone()),@@ -5910,24 +5930,11 @@ where                     }                     // Anything else                     //-                    // Parse error. Ignore the token.-                    _ => match token {-                        Token::StartTag { tag_name, .. } => {-                            self.errors.push(Error::new(-                                token_and_info.span,-                                ErrorKind::StrayStartTag(tag_name.clone()),-                            ));-                        }-                        Token::EndTag { tag_name, .. } => {-                            self.errors.push(Error::new(-                                token_and_info.span,-                                ErrorKind::StrayEndTag(tag_name.clone()),-                            ));-                        }-                        _ => {-                            unreachable!()-                        }-                    },+                    // Process the token using the rules for the "in body" insertion mode.+                    // This preserves the button and rich option content of customizable selects.+                    _ => {+                        self.process_token_using_rules(token_and_info, InsertionMode::InBody)?;+                    }                 }             }             // The "in select in table" insertion mode

deps/swc/crates/swc_native_addon/Cargo.toml

new file mode 100644@@ -0,0 +1,42 @@+[package]+name = "swc_native_addon"+version = "0.1.0"+edition = "2021"+rust-version = "1.85"+license = "Apache-2.0"+publish = false+description = "Private verified native addon payload and materialization support"++[dependencies]+blake3 = "=1.5.4"+sha2 = "=0.10.9"+tempfile = "=3.12.0"+tracing = { version = "=0.1.44", default-features = false, features = ["std"] }+zstd = { version = "=0.13.3", default-features = false }++# zstd's C build enables cc's parallel feature. Pin this transitive host+# dependency to a release compatible with the carrier's Rust 1.85 MSRV.+[build-dependencies]+jobserver = "=0.1.35"++[package.metadata.cargo-shear]+ignored = ["jobserver"]++[target.'cfg(unix)'.dependencies]+libc = "=0.2.189"+xattr = "=1.3.1"++[target.'cfg(all(target_os = "macos", target_arch = "x86_64"))'.dependencies]+blake3 = { version = "=1.5.4", features = ["rayon"] }+# Keep the existing rayon-core pin for reproducible carrier builds.+rayon-core = "=1.12.1"++[target.'cfg(windows)'.dependencies]+windows-sys = { version = "=0.52.0", features = [+  "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization",+  "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Ioctl",+  "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Threading",+] }++[dev-dependencies]+libloading = "=0.8.9"

deps/swc/crates/swc_native_addon/README.md

new file mode 100644@@ -0,0 +1,278 @@+# Private native addon carriers++This private implementation packages an already stripped native addon into a+single target carrier for issue #12273. The npm release workflow finalizes and+verifies carriers before uploading artifacts, then checks the complete release+before publishing. The existing raw addon build, generated JS/DTS, package names,+and sibling `swc` executable are not changed.++## Build contract++Run the host packer **after the final raw-addon strip/signing steps**:++```sh+cargo run -p swc_native_addon_pack -- \+  --input /absolute/final-stripped.node --output /absolute/payload.swcn \+  --target x86_64-unknown-linux-gnu+SWC_NATIVE_BINDING_PAYLOAD=/absolute/payload.swcn \+  cargo build -p binding_native_addon --release --features embedded-payload \+  --target x86_64-unknown-linux-gnu+```++`SWC_NATIVE_BINDING_PAYLOAD` is a build input, not a runtime cache setting.+The packer records the carrier target, and the build script requires it to match+Cargo's `TARGET` before validating and snapshotting it into `OUT_DIR`. Without the explicit+feature, the crate can participate in workspace checks but has no usable payload;+loading that development carrier throws an error. An enabled build without a+valid payload fails. No unpublished napi-rs dependency is needed.++The carrier allowlist is x86_64/aarch64 on Apple Darwin, Windows MSVC, and Linux+GNU/musl. Other npm targets stay raw. Release integration stages,+strips/signs, validates, and size-checks the completed carrier before atomically+replacing only the selected `.node` artifact. A carrier that is not smaller than+the stripped addon must fail that integration step. Never strip or otherwise+modify the raw bytes after packing them.++The generated build input retains the complete payload in a mapped read-only+section (`.swc_native` on ELF, `__TEXT,__swc_native` on Mach-O, and `.swcn`+on PE). This keeps final-artifact inspection independent of optimizer decisions+about constant header reads. It does not change the version 1 payload format.+The host `swc-native-addon-verify` binary validates that section, target, exports,+decoded bytes, and size. `--raw` compares against the final stripped input;+`--extract` creates a new verified comparison image; `--replace` requires+`--raw` and atomically replaces only that verified `.node` destination.++The build also retains private runtime integrity metadata in `.swc_integrity`+(ELF), `__TEXT,__swc_integrity` (Mach-O), or `.swci` (PE). Its 136 bytes contain+`SWCNB3V1`, the complete 96-byte v1 header, and a 32-byte BLAKE3 digest derived+from SHA-512-verified raw bytes. Runtime decoding and cache reads bind that+metadata to the payload header before verifying every byte with BLAKE3 1.5.4.+The final artifact verifier checks both digests against the decoded image.+Ordinary payload decoding and `Header::verify` retain their SHA-512 defaults;+npm artifacts, reports, and cache filenames keep their SHA-512 identities.++See [the release and user guide](../../docs/native-addon-carriers.md) for artifact+contracts, the shared publishing gate, and CI measurements.++## Version 1 format++All integers are little-endian, independent of Rust layout and target byte order.++| Offset | Bytes | Field |+| --- | --- | --- |+| 0 | 8 | ASCII `SWCNZSTD` |+| 8 | 2 | Version, `1` |+| 10 | 2 | Header size, `96` |+| 12 | 1 | Supported carrier target identifier |+| 13 | 3 | Reserved, all zero |+| 16 | 8 | Exact compressed frame length |+| 24 | 8 | Exact raw addon length |+| 32 | 64 | SHA-512 of the raw addon |+| 96 | variable | One level-16 zstd frame |++Both lengths must be nonzero and at most 2 GiB. The zstd frame must declare the+same raw size and cannot use a dictionary, skippable frame, concatenated frames,+or trailing bytes. Decoding bounds the window by the declared output size and+streams through a fixed-size buffer; it never reserves the claimed raw size up+front. Length, digest, and native magic validation finish before publication.+ELF/Mach-O magic is checked, and PE requires a bounded DOS offset and `PE\0\0`+signature, not merely `MZ`. The OS loader remains responsible for complete image+format, architecture, dynamic dependencies, and signing validation.++The digest proves equality with the trusted installed carrier's raw addon. It+does not authenticate an untrusted carrier or defend against a process with the+same user's ability to modify arbitrary code files. Corrupt cache contents are+never treated as executable code merely because their filename is a digest.++## Runtime and cache++The carrier resolves its own image using `dladdr` on Unix and+`GetModuleHandleExW`/`GetModuleFileNameW` on Windows. Payload bytes come from the+mapped image, not a pathname that another process might have replaced already.+No N-API 9 path API is used. Only original N-API error functions are required, and+the advertised N-API version is 3, matching the existing bindings. Package Node+minimums remain core 10, minifier 12, HTML 14, and React Compiler 20.++Node 10.0 also needs the original constructor-based `napi_module_register` hook;+exporting `napi_register_module_v1` alone is insufficient on that runtime. The+constructor only registers a static descriptor. It does no decoding or file IO,+and the normal initializer still forwards the exact raw registration call.++The raw initializer receives the original environment and exports pointers. Its+result is returned unchanged, including a distinct exports object or null with a+pending exception. Initialization failures throw `ERR_SWC_NATIVE_*` errors with+operation/path context and remediation; they do not return empty exports. Loaded+libraries remain resident for the process lifetime because callbacks can outlive+registration or any one Node environment. Initialization locks are released+before invoking addon registration.++`SWC_NATIVE_BINDING_CACHE` selects the materialization mode and cache root:++| Value | Behavior |+| --- | --- |+| Unset or empty | User-isolated, content-addressed user cache |+| `0` | Temporary materialization only in the user cache; never self-replace the carrier |+| Absolute directory | Custom root; fall back to the user cache if unusable |+| Any other relative value | Throw a configuration error |++On Unix, `SWC_NATIVE_BINDING_CACHE_SKIP_SECURITY_CHECK=1` skips the ownership,+permission, and symlink checks on the cache root and every ancestor. Unset,+empty, and all other values retain those checks. Windows ignores this variable.+The opt-out applies to default and custom roots, fallback roots, and temporary+mode. For example, an isolated Jenkins executor can use:++```sh+SWC_NATIVE_BINDING_CACHE_SKIP_SECURITY_CHECK=1 \+    SWC_NATIVE_BINDING_CACHE=/absolute/jenkins/cache node build.js+```++Use this only when all root components are trusted: another user with access+to replace an ancestor could redirect the verified addon path before loading.+The root must still be absolute and writable on an executable filesystem.+Private namespace directories, cache-file checks, payload and cache-image+integrity verification, locking, and atomic publication remain enforced. This+option does not make shared or symlinked directories inside the private cache+namespace acceptable.++The default user cache avoids hardened system temporary mounts that are `noexec`;+Linux rejects a user cache mounted `noexec` before attempting to load from it.+Windows first uses `%LOCALAPPDATA%\swc`. If resolving or using that cache fails,+it retries `%USERPROFILE%\.swc-cache` for cache errors only, including in mode+`0` and after a custom root fails. Both paths must be absolute and pass the same+ownership, ACL, and reparse-point checks. LocalAppData can inherit AppContainer+replacement grants, so the profile fallback preserves validation without+changing the original root's ACL. A usable primary cache does not consult+`USERPROFILE`. If neither candidate is safe and writable, the error includes+both causes and recommends setting `SWC_NATIVE_BINDING_CACHE` to a safe absolute+directory. Payload or cleanup-helper integrity failures never trigger the+profile retry.+Under either persistent root, entries live in `swc-native-<effective UID or user+SID>/v1/<128 hexadecimal SHA-512 digits>.node`. Unix directories are owner-only;+Windows directories have protected owner/SYSTEM DACLs. Ancestor directories+may also belong to Administrators or TrustedInstaller. Effective untrusted+replacement grants are rejected with their directory and SID; inheritance-only+ACEs are checked at the descendants to which they apply. Unsafe cache files,+symlinks/reparse points inside the private namespace, and (unless explicitly+opted out on Unix) roots with an unsafe ancestor are rejected. A normal corrupt+regular entry is replaced with newly decoded, verified bytes. If both custom+and default roots fail, the loader throws rather than silently loading+unverified data.++Every cache hit is checked for native magic, length, and BLAKE3 over the entire+file. On x64 macOS, verification batches use at most 32 MiB of temporary scratch+memory to avoid repeated worker dispatch under Rosetta; larger images still+stream in bounded batches. The SHA-512 cache key remains unchanged. Per-digest OS+file locks coordinate checking, repair, publication, and native loading. Writers+use unique staging files in the destination directory, write and verify them,+then rename atomically. Canonical entries are never truncated in place. Closing+the lock handle releases coordination after failures or process death, without+stale PID locks. Staging files abandoned by abrupt termination are never cache+hits; they may be removed when the owning user cleans the temporary directory.+Cache publication does not force durable storage: missing or damaged entries+after power loss are rebuilt after full-byte verification. Installed-carrier+replacement retains its file and directory durability flushes.+Each persistent namespace retains at most three inactive-or-current raw addon+images. A short namespace lock coordinates eviction with per-digest locks, so+an image being loaded is deferred until a later materialization.++In mode `0`, each materialization has a unique process-prefixed filename. Unix+unlinks it after successful `dlopen` while retaining the mapped library. Windows+closes the writable decoder handle and starts an embedded native cleanup worker+before loading. The worker waits for EOF on a private pipe and retries deletion+for up to ten seconds while Windows releases the mapped image. The pipe closes+on both normal and forced process termination; Rust destructors are not required.+The small helper executable persists in the private cache, is verified over all+bytes before each launch, and requires no shell or additional installed runtime.+The worker is used only for temporary images, not persistent cache hits. If an+external process keeps the image mapped beyond the retry limit, or terminates+the cleanup worker too, the temporary file can remain for manual cache cleanup.++## Transparent filesystem compression++On writable btrfs, persistent modes first try self-replacement. The macOS+loader instead uses the verified cache directly, before even resolving the+carrier path: synchronous APFS recompression and decoding a second image make+the first load after installation unnecessarily expensive. macOS retains the+zstd carrier and a separate uncompressed cache image, trading disk space for+startup latency.+Cache verification, repair, and temporary mode are unchanged. The low-level APFS+replacement helper remains available for explicit filesystem tests; it is not+part of normal macOS loading.++When self-replacement is attempted, only a completed compressed staging file+beside the carrier is atomically renamed into place. The original mapped inode+is never modified. The current process loads a+separate verified temporary image, avoiding recursive lookup of the already+mapped carrier. Competing first loads lock the original inode and recheck its+identity before replacement. Hardlinked or differently owned images use the+cache path instead. Package-manager updates do not participate in these advisory+locks, so replacement remains a best-effort optimization of a stable installation.++APFS uses Apple's installed `/usr/bin/ditto --hfsCompression` on staging files,+then verifies transparent readback and `UF_COMPRESSED`. btrfs sets compression+policy and explicitly recompresses written extents; setting an inode flag alone+would not compress existing bytes. Unsupported/read-only filesystems and failed+compression fall back to the verified cache. The sibling CLI is never touched.+Replacement preserves ownership, permissions, and extended security attributes.+An already matching inherited security label is left alone; failure to preserve+a differing label disables replacement. Compression-specific resource metadata+is regenerated for the raw image instead of copied from the carrier.+On macOS, a carrier with an ACL not equivalent to its mode bits uses the verified+cache instead, because staging replacement cannot safely preserve that ACL.++Windows never replaces a loaded carrier DLL. New persistent and process-local+cache images are ordinary, uncompressed files: first loading an NTFS-compressed+DLL adds substantial startup latency. Before decoding, the loader clears any+compression inherited from the cache directory through the private staging+file's existing write handle. Ordinary files require no compression control call,+and the user's directory policy remains unchanged. Failure to prepare the image+uses the usual cache error/fallback path. Full-byte verification still precedes+publication and loading. Existing compressed cache entries remain immutable and+are fully verified on every hit; corrupt entries are replaced by new ordinary+images. The npm carrier's zstd payload is unchanged.++## Verification++```sh+git submodule update --init --recursive+cargo test -p swc_native_addon -p swc_native_addon_pack -p binding_native_addon+python3 crates/swc_native_addon/scripts/msrv.py+cargo fmt --all+cargo clippy --all --all-targets -- -D warnings+```++The MSRV script copies only these private components into a temporary workspace+and uses Rust 1.85 with the existing dependency versions. SWC's main workspace+uses a v4 lockfile and nightly flags, so those settings cannot directly serve as+the MSRV harness. The script leaves the repository lockfile and configuration+unchanged. Fixtures are compiled from Rust source and stripped during testing;+they are real native libraries, not byte arrays pretending to be addons.++Run the carrier smoke suite using each package minimum, for example:++```sh+SWC_TEST_NODE=/absolute/node-10.0.0/bin/node \+  cargo test -p binding_native_addon --test carrier+```++On matching hosts, explicitly run the filesystem tests:++```sh+SWC_TEST_VOLUME=/writable/apfs \+  cargo test -p swc_native_addon --test platform apfs_self_replacement -- --ignored --exact+SWC_TEST_VOLUME=/writable/btrfs \+  cargo test -p swc_native_addon --test platform btrfs_self_replacement -- --ignored --exact+cargo test -p swc_native_addon --test platform ntfs_compression -- --ignored --exact+cargo test -p swc_native_addon --test ntfs -- --ignored+```++Cross-target `cargo check` validates Rust adapters but cannot establish native+loading, signing, filesystem compression, or Windows deletion semantics. Report+those tests as unavailable, with the actual host/tool/filesystem reason, whenever+they cannot run. This implementation was written afresh; no PR #12000 or+MIT-licensed PoC implementation was copied.+The original Node.js registration-interface attribution is retained in+`bindings/binding_native_addon/NOTICE` and must accompany distributed carriers.++See [VERIFICATION.md](VERIFICATION.md) for the implementation run's results and+the native platform checks that still require matching hosts.

deps/swc/crates/swc_native_addon/VERIFICATION.md

new file mode 100644@@ -0,0 +1,85 @@+# Implementation verification++Verified on 2026-09-07 in the hosted Linux x86_64 runner, starting from SWC main+`a5bd5924c14e702b995cdbb6581a9ecf4098df5a`. This records observed results, not a+claim that unexecuted native platform tests passed.++## Passed++- `cargo fmt --all`.+- `cargo test -p swc_native_addon -p swc_native_addon_pack -p binding_native_addon+  --target-dir target/native-node`: 17 tests passed; the three explicit filesystem+  tests were ignored pending their required volumes/hosts.+- `cargo clippy -p swc_native_addon -p swc_native_addon_pack+  -p binding_native_addon --all-targets --target-dir target/native-lints --+  -D warnings`.+- `python3 crates/swc_native_addon/scripts/msrv.py`: the same private suites+  passed with Rust 1.73.0 and the repository's dependency versions in a temporary+  standalone workspace.+- `SWC_TEST_NODE=/tmp/swc-native-node/node-v<VERSION>-linux-x64/bin/node+  cargo test -p binding_native_addon --test carrier --target-dir target/native-node`+  passed separately for 10.0.0, 12.0.0, 14.0.0, and 20.0.0. The default Node 24.20.0+  run passed too. These exercise exact exports forwarding, live callbacks, raw+  exceptions, loader errors, and simultaneous first loads in real Node processes.+- In `packages/core`, `pnpm build:dev` and then `pnpm test`: 40 test files and+  117 tests passed; two files and three tests were skipped by the existing suite.+  The initially missing `wasm32-wasip1` target was installed before the successful+  retry. Generated public binding and fixture files were restored afterward.++The actual core development addon was copied, stripped with `strip --strip-all`,+packed, and embedded into a release carrier. Under Node 24.20.0 with+`SWC_NATIVE_BINDING_CACHE=0`, `getTargetTriple()` returned+`x86_64-unknown-linux-gnu` and `transformSync` successfully transformed JavaScript.+The stripped raw file was 95,439,432 bytes, its payload 20,488,224 bytes, and the+release carrier 21,073,680 bytes. These are one local artifact's measurements,+not a release-wide size or performance guarantee.++## Cross-target Rust checks++This command passed for each target below:++```sh+ZSTD_SYS_USE_PKG_CONFIG=1 PKG_CONFIG_ALLOW_CROSS=1 \+  cargo clippy -p swc_native_addon -p binding_native_addon --all-targets \+  --target <TARGET> --target-dir target/native-cross -- -D warnings+```++- `x86_64-pc-windows-msvc`+- `aarch64-pc-windows-msvc`+- `x86_64-apple-darwin`+- `aarch64-apple-darwin`+- `x86_64-unknown-linux-musl`+- `aarch64-unknown-linux-musl`+- `aarch64-unknown-linux-gnu`++Host pkg-config supplied zstd metadata only for these Rust checks. They did not+link target zstd/native images and do not verify native loading, filesystem+behavior, signing, or Windows process-exit cleanup.++## Blocked and unavailable commands++`git submodule update --init --recursive` was attempted before tests. The runner+rejected it with: "git submodule update is disabled by the runner because each+submodule requires an independently bound repository credential". The missing+submodules were not cloned through another route.++`cargo clippy --all --all-targets -- -D warnings` was run. It could not complete:+`crates/swc_html_parser/tests/html5lib_tests.rs` at lines 61 and 540 reported+fixture-macro panics, "No test found", because the `html5lib-tests` submodule is+empty. Those failures also cause unused-import diagnostics in that test. The+submodule authorization restriction above prevents repairing this environment.++The following native commands remain unavailable:++| Command | Exact reason |+| --- | --- |+| `SWC_TEST_VOLUME=/writable/apfs cargo test -p swc_native_addon --test platform apfs_self_replacement -- --ignored --exact` | The host is Linux, with no APFS volume or macOS runtime. Apple's `/usr/bin/ditto` is unavailable. |+| `SWC_TEST_VOLUME=/writable/btrfs cargo test -p swc_native_addon --test platform btrfs_self_replacement -- --ignored --exact` | The workspace and OS-temp filesystem are overlayfs. A btrfs image was prepared, but `sudo mount -o loop /tmp/swc-btrfs-test-57qzc5li/volume.img /tmp/swc-btrfs-test-57qzc5li/mount` failed because sudo requires a terminal/password. No writable btrfs mount is available. |+| `cargo test -p swc_native_addon --test platform ntfs_compression -- --ignored --exact` | The host has neither a Windows runtime nor an NTFS volume on which the Win32 compression API can run. |+| `cargo test -p swc_native_addon --test platform temporary_cleanup_after_process_exit -- --exact` on Windows | This Windows-only test is excluded on Linux. Normal-exit and forced-termination cleanup require a real Windows process and filesystem. |+| `cargo test -p binding_native_addon --test carrier` on macOS | There is no macOS runtime, Apple SDK, or `/usr/bin/codesign`; Linux cross-target linting cannot execute the Mach-O carrier or its signing checks. |+| `cargo test -p binding_native_addon --test carrier` on Windows | There is no Windows runtime or MSVC native link environment for loading the PE carrier. Rust type checking alone does not exercise Windows loader locks or deletion sharing. |++The explicit APFS, btrfs, and NTFS tests remain ignored by default so an ordinary+test run does not misrepresent fallback behavior as successful compression.+Run these native checks before activating carriers in npm release publishing.

deps/swc/crates/swc_native_addon/build.rs

new file mode 100644@@ -0,0 +1,38 @@+use std::{env, path::PathBuf, process::Command};++fn main() {+    println!("cargo:rerun-if-changed=src/cleanup/windows_helper.rs");+    if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {+        return;+    }+    // Compile for the addon target, not the build-script host. The helper has+    // no external dependencies and is embedded in the carrier, so temporary+    // loading does not depend on a shell, Node executable, or installed tool.+    let output = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("cleanup.exe");+    let mut compiler = Command::new(env::var_os("RUSTC").unwrap());+    compiler.args([+        "src/cleanup/windows_helper.rs",+        "--edition=2021",+        "--crate-name=swc_native_cleanup",+        "-Copt-level=s",+        "-Cpanic=abort",+        "-Ctarget-feature=+crt-static",+        "-Clto",+        "-Ccodegen-units=1",+        "-Cstrip=symbols",+        "--target",+    ]);+    compiler+        .arg(env::var_os("TARGET").unwrap())+        .arg("-o")+        .arg(output);+    if let Some(linker) = env::var_os("RUSTC_LINKER") {+        let mut option = std::ffi::OsString::from("linker=");+        option.push(linker);+        compiler.arg("-C").arg(option);+    }+    assert!(+        compiler.status().unwrap().success(),+        "build native cleanup helper"+    );+}

deps/swc/crates/swc_native_addon/scripts/msrv.py

new file mode 100644@@ -0,0 +1,38 @@+#!/usr/bin/env python3+"""Verify the private foundation with Rust 1.85 without changing SWC's lockfile.++The main workspace has nightly flags and a Cargo v4 lockfile. This disposable+workspace uses the same sources and dependency versions, with v3 serialization.+No dependency versions are guessed or updated by the MSRV check.+"""++import os+from pathlib import Path+import shutil+import subprocess+import tempfile+++repository = Path(__file__).resolve().parents[3]+members = [+    "crates/swc_native_addon",+    "tools/swc-native-addon-pack",+    "bindings/binding_native_addon",+]+with tempfile.TemporaryDirectory(prefix="swc-native-msrv-") as temporary:+    root = Path(temporary)+    for member in members:+        shutil.copytree(repository / member, root / member)+    manifest = '[workspace]\nresolver = "2"\nmembers = [\n'+    manifest += "".join(f'  "{member}",\n' for member in members) + "]\n"+    (root / "Cargo.toml").write_text(manifest)+    lock = (repository / "Cargo.lock").read_text()+    (root / "Cargo.lock").write_text(lock.replace("version = 4\n", "version = 3\n", 1))+    environment = os.environ.copy()+    environment["RUSTFLAGS"] = ""+    environment["RUSTDOCFLAGS"] = ""+    environment.pop("CARGO_ENCODED_RUSTFLAGS", None)+    environment["RUSTUP_TOOLCHAIN"] = "1.85.0"+    # This lockfile also contains unrelated workspace packages, which Cargo+    # prunes in the disposable copy. Dependency versions remain pinned.+    subprocess.run(["cargo", "test", "--workspace"], cwd=root, env=environment, check=True)

deps/swc/crates/swc_native_addon/src/cache.rs

new file mode 100644@@ -0,0 +1,361 @@+//! Cache entries are immutable once published. The lock covers validation,+//! repair, publication, and dlopen/LoadLibrary, but never addon registration.+//! A digest is checked on every cache hit; a familiar filename is not proof.++use std::{+    env,+    ffi::OsString,+    fs::{self, File},+    io,+    path::{Path, PathBuf},+    time::SystemTime,+};++use crate::{format::Payload, platform, Error, ErrorKind, Result};++const MAX_PERSISTENT_IMAGES: usize = 3;+const PRUNE_LOCK: &str = ".swc-native-prune.lock";++#[derive(Clone, Debug, Eq, PartialEq)]+pub enum CacheMode {+    Default,+    Temporary,+    Custom(PathBuf),+}++impl CacheMode {+    pub fn from_env() -> Result<Self> {+        Self::parse(env::var_os("SWC_NATIVE_BINDING_CACHE"))+    }++    pub fn parse(value: Option<OsString>) -> Result<Self> {+        match value {+            None => Ok(Self::Default),+            Some(value) if value.is_empty() => Ok(Self::Default),+            Some(value) if value == "0" => Ok(Self::Temporary),+            Some(value) => {+                let path = PathBuf::from(value);+                if !path.is_absolute() {+                    return Err(Error::new(+                        ErrorKind::Configuration,+                        "SWC_NATIVE_BINDING_CACHE must be 0 or an absolute directory; unset it to \+                         use the default user cache",+                    ));+                }+                Ok(Self::Custom(path))+            }+        }+    }+}++/// Owns staging cleanup and cache coordination until loading has succeeded.+/// On Windows a cleanup worker is armed before loading when execution is+/// allowed.+pub struct Materialized {+    path: PathBuf,+    temporary: bool,+    lock: Option<File>,+    cache_directory: Option<PathBuf>,+    #[cfg(windows)]+    _cleanup: Option<crate::cleanup::Cleanup>,+}++impl Materialized {+    pub fn path(&self) -> &Path {+        &self.path+    }++    /// Call immediately after successful native loading, before registration.+    pub fn loaded(&mut self) -> Result<()> {+        #[cfg(unix)]+        if self.temporary {+            fs::remove_file(&self.path)+                .map_err(|e| Error::io(ErrorKind::Cache, "unlink loaded temporary addon", e))?;+            self.temporary = false;+        }+        self.lock = None;+        if let Some(directory) = &self.cache_directory {+            match namespace_lock(directory) {+                Ok(namespace) => {+                    if let Err(error) = prune(directory, &self.path) {+                        tracing::debug!(path = %directory.display(), %error, "native cache pruning failed");+                    }+                    drop(namespace);+                }+                Err(error) => {+                    tracing::debug!(path = %directory.display(), %error, "native cache pruning lock unavailable");+                }+            }+        }+        Ok(())+    }+}++impl Drop for Materialized {+    fn drop(&mut self) {+        if self.temporary {+            // The Windows worker retries after the pipe closes if an image+            // section still prevents this immediate, best-effort deletion.+            let _ = fs::remove_file(&self.path);+        }+    }+}++fn io_error(operation: &str, path: &Path, error: io::Error) -> Error {+    Error::io(+        ErrorKind::Cache,+        &format!("{operation} {}", path.display()),+        error,+    )+}++/// The user namespace is used for custom roots too, so explicitly shared parent+/// directories cannot accidentally turn the cache into a cross-user code+/// source.+pub fn cache_directory(root: &Path) -> Result<PathBuf> {+    platform::executable_cache_root(root)?;+    platform::create_cache_root(root).map_err(|e| io_error("create cache root", root, e))?;+    platform::secure_cache_root(root).map_err(|e| io_error("validate cache root", root, e))?;+    let user = root.join(platform::user_namespace()?);+    platform::private_directory(&user).map_err(|e| io_error("secure cache directory", &user, e))?;+    let version = user.join("v1");+    platform::private_directory(&version)+        .map_err(|e| io_error("secure cache directory", &version, e))?;+    Ok(version)+}++/// Try the usual user cache first, then the platform fallback. Shared ancestors+/// can make Unix home directories unsafe, as can ordinary Windows LocalAppData+/// ACLs. Retry only cache failures, never payload corruption, and validate both+/// roots using the same security checks. Primary success does no extra work.+fn with_user_cache_root<T>(materialize: impl Fn(&Path) -> Result<T>) -> Result<T> {+    let primary = platform::user_cache_root().and_then(|root| materialize(&root));+    if let Err(primary) = primary {+        if primary.kind != ErrorKind::Cache {+            return Err(primary);+        }+        tracing::debug!(error = %primary, "native user cache unavailable; trying fallback cache");+        return platform::fallback_cache_root()+            .and_then(|root| materialize(&root))+            .map_err(|fallback| {+                if fallback.kind != ErrorKind::Cache {+                    return fallback;+                }+                Error::new(+                    ErrorKind::Cache,+                    format!(+                        "user cache failed: {primary}; fallback cache failed: {fallback}; set \+                         SWC_NATIVE_BINDING_CACHE to a safe absolute directory owned by your user \+                         whose ancestors do not grant other users replacement rights"+                    ),+                )+            });+    }+    primary+}++pub fn materialize(payload: &Payload<'_>, mode: &CacheMode) -> Result<Materialized> {+    match mode {+        CacheMode::Temporary => temporary(payload),+        CacheMode::Default => with_user_cache_root(|root| cached_at(payload, root)),+        CacheMode::Custom(root) => match cached_at(payload, root) {+            Ok(file) => Ok(file),+            Err(custom) => {+                tracing::debug!(path = %root.display(), error = %custom, "custom native cache unavailable; using user cache");+                with_user_cache_root(|root| cached_at(payload, root)).map_err(|default| {+                    Error::new(+                        ErrorKind::Cache,+                        format!(+                            "custom cache failed: {custom}; default cache failed: {default}; set \+                             SWC_NATIVE_BINDING_CACHE to a safe writable absolute directory or \+                             unset it to use the default user cache"+                        ),+                    )+                })+            }+        },+    }+}++/// Materialize without consulting or creating any persistent digest entry.+pub fn temporary(payload: &Payload<'_>) -> Result<Materialized> {+    with_user_cache_root(|root| temporary_at(payload, root))+}++fn temporary_at(payload: &Payload<'_>, root: &Path) -> Result<Materialized> {+    let dir = cache_directory(root)?;+    let mut file =+        platform::temporary_file(&dir, &format!("process-{}-", std::process::id()), ".node")+            .map_err(|e| io_error("create temporary addon", &dir, e))?;+    platform::prepare_cache_image(file.as_file())+        .map_err(|e| io_error("prepare temporary addon", file.path(), e))?;+    payload.decode_into(file.as_file_mut())?;+    // File writes are already visible to the image loader. This process-local+    // image does not need to survive power loss, so no durability flush is needed.+    // Close the writable file before mapping an image on Windows. Retaining a+    // write handle can conflict with the OS image loader's sharing requirements.+    let path = file.into_temp_path();+    // Arm cleanup before LoadLibrary. The pipe writer is not inherited by+    // other children and closes even on forced termination of this process.+    #[cfg(windows)]+    let cleanup = crate::cleanup::arm(&path).map_err(|error| {+        // Worker verification reports corrupt bytes as InvalidData. This is+        // an integrity failure, not an unusable root: do not hide it by trying+        // a different cache, just as we never retry a corrupt addon payload.+        let kind = if error.kind() == io::ErrorKind::InvalidData {+            ErrorKind::Integrity+        } else {+            ErrorKind::Cache+        };+        Error::io(+            kind,+            &format!("arm temporary addon cleanup {}", path.display()),+            error,+        )+    })?;+    let path = path+        .keep()+        .map_err(|e| io_error("retain temporary addon", &dir, e.error))?;+    Ok(Materialized {+        path,+        temporary: true,+        lock: None,+        cache_directory: None,+        #[cfg(windows)]+        _cleanup: cleanup,+    })+}++fn namespace_lock(directory: &Path) -> Result<File> {+    let path = directory.join(PRUNE_LOCK);+    let lock = platform::open_regular(&path, true, true)+        .map_err(|e| io_error("open cache pruning lock", &path, e))?;+    platform::lock_exclusive(&lock).map_err(|e| io_error("lock cache pruning", &path, e))?;+    Ok(lock)+}++fn is_cache_key(path: &Path) -> bool {+    path.extension()+        .is_some_and(|extension| extension == "node")+        && path.file_stem().is_some_and(|stem| {+            stem.to_str().is_some_and(|stem| {+                stem.len() == 128 && stem.bytes().all(|byte| byte.is_ascii_hexdigit())+            })+        })+}++fn prune(directory: &Path, current: &Path) -> Result<()> {+    let mut entries = Vec::new();+    for entry in+        fs::read_dir(directory).map_err(|e| io_error("enumerate cache entries", directory, e))?+    {+        let entry = entry.map_err(|e| io_error("read cache entry", directory, e))?;+        let path = entry.path();+        if path != current && is_cache_key(&path) {+            let modified = entry+                .metadata()+                .and_then(|metadata| metadata.modified())+                .unwrap_or(SystemTime::UNIX_EPOCH);+            entries.push((modified, path));+        }+    }+    // Include the current image in the budget. Older images are evicted first.+    let mut remaining = entries.len() + 1;+    if remaining <= MAX_PERSISTENT_IMAGES {+        return Ok(());+    }+    entries.sort_unstable_by_key(|(modified, _)| *modified);+    for (_, image) in entries {+        if remaining <= MAX_PERSISTENT_IMAGES {+            break;+        }+        let Some(key) = image.file_stem() else {+            continue;+        };+        let lock_path = directory.join(key).with_extension("lock");+        let lock = match platform::open_regular(&lock_path, true, false) {+            Ok(lock) => lock,+            Err(error) => {+                tracing::debug!(path = %lock_path.display(), %error, "skip cache entry without a usable pruning lock");+                continue;+            }+        };+        match platform::try_lock_exclusive(&lock) {+            Ok(true) => {}+            Ok(false) => continue,+            Err(error) => {+                tracing::debug!(path = %lock_path.display(), %error, "skip cache entry whose pruning lock cannot be acquired");+                continue;+            }+        }+        match fs::remove_file(&image) {+            Ok(()) => {+                remaining -= 1;+                if let Err(error) = fs::remove_file(&lock_path) {+                    tracing::debug!(path = %lock_path.display(), %error, "remove stale native cache lock");+                }+            }+            Err(error) if error.kind() == io::ErrorKind::NotFound => {+                remaining -= 1;+            }+            Err(error) => {+                tracing::debug!(path = %image.display(), %error, "skip native cache entry that cannot be pruned");+            }+        }+    }+    Ok(())+}++/// Public within the private crate API to allow isolated-root concurrency+/// tests.+pub fn cached_at(payload: &Payload<'_>, root: &Path) -> Result<Materialized> {+    let directory = cache_directory(root)?;+    let key = payload.header.cache_key();+    let path = directory.join(format!("{key}.node"));+    let lock_path = directory.join(format!("{key}.lock"));+    // Coordinate lock-file creation with pruning. The per-entry lock remains+    // held after this short critical section, through native loading.+    let namespace = namespace_lock(&directory)?;+    let lock = platform::open_regular(&lock_path, true, true)+        .map_err(|e| io_error("open cache lock", &lock_path, e))?;+    platform::lock_exclusive(&lock).map_err(|e| io_error("lock cache entry", &lock_path, e))?;+    drop(namespace);+    let valid = match platform::open_regular(&path, false, false) {+        Ok(mut file) => match payload.verify_image(&mut file) {+            Ok(()) => true,+            Err(error) => {+                tracing::warn!(path = %path.display(), %error, "rejecting corrupt native cache entry; rebuilding from payload");+                false+            }+        },+        Err(e) if e.kind() == io::ErrorKind::NotFound => false,+        // Never follow or repair an attacker-controlled special file. A custom+        // root can still fall back to the private default root.+        Err(e) => return Err(io_error("open cache entry", &path, e)),+    };+    if !valid {+        let mut staged = platform::temporary_file(&directory, "publish-", ".tmp")+            .map_err(|e| io_error("stage cache entry", &directory, e))?;+        platform::prepare_cache_image(staged.as_file())+            .map_err(|e| io_error("prepare staged cache image", staged.path(), e))?;+        payload.decode_into(staged.as_file_mut())?;+        payload.verify_image(staged.as_file_mut())?;+        // All cooperating readers hold the digest lock, including during repair.+        // No writer ever truncates the canonical filename in place.+        let staged = staged.into_temp_path();+        platform::replace_file(&staged, &path)+            .map_err(|e| io_error("atomically publish cache entry", &path, e))?;+        // A cache is recoverable, unlike the installed carrier. Atomic rename+        // protects concurrent readers; full-byte verification repairs missing,+        // truncated, or corrupt entries after power loss. Avoid forcing either+        // the file or directory to stable storage on the startup path.+    }+    Ok(Materialized {+        path,+        temporary: false,+        lock: Some(lock),+        cache_directory: Some(directory),+        #[cfg(windows)]+        _cleanup: None,+    })+}

deps/swc/crates/swc_native_addon/src/cleanup/mod.rs

new file mode 100644@@ -0,0 +1,85 @@+//! Windows cannot unlink a mapped DLL. A small verified native worker waits+//! for a pipe to close at process teardown, then removes that unique image.++use std::{+    fs::File,+    io::{self, Read, Write},+    os::windows::{ffi::OsStrExt, process::CommandExt},+    path::Path,+    process::{Child, ChildStdin, Command, Stdio},+};++use crate::platform;++const HELPER: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/cleanup.exe"));++pub(crate) struct Cleanup {+    // Closing stdin triggers the helper; dropping Child does not wait for it.+    _input: ChildStdin,+    _child: Child,+}++fn verified_helper(directory: &Path) -> io::Result<(std::path::PathBuf, File)> {+    let digest = blake3::hash(HELPER);+    let path = directory.join(format!("cleanup-{digest}.exe"));+    let lock = platform::open_regular(&directory.join(".cleanup.lock"), true, true)?;+    platform::lock_exclusive(&lock)?;+    let mut file = match platform::open_regular(&path, false, false) {+        Ok(file) => file,+        Err(error) if error.kind() == io::ErrorKind::NotFound => {+            let mut stage = platform::temporary_file(directory, "cleanup-", ".tmp")?;+            stage.write_all(HELPER)?;+            let stage = stage.into_temp_path();+            platform::replace_file(&stage, &path)?;+            platform::open_regular(&path, false, false)?+        }+        Err(error) => return Err(error),+    };+    // The filename alone never establishes trust, including on later starts.+    let mut bytes = Vec::new();+    (&mut file)+        .take(HELPER.len() as u64 + 1)+        .read_to_end(&mut bytes)?;+    if bytes.len() != HELPER.len() || blake3::hash(&bytes) != digest {+        return Err(io::Error::new(+            io::ErrorKind::InvalidData,+            "native cleanup helper is corrupt",+        ));+    }+    Ok((path, file))+}++pub(crate) fn arm(path: &Path) -> io::Result<Option<Cleanup>> {+    let directory = path.parent().ok_or(io::ErrorKind::InvalidInput)?;+    let (helper, _verified) = verified_helper(directory)?;+    let mut child = match Command::new(&helper)+        .stdin(Stdio::piped())+        .stdout(Stdio::null())+        .stderr(Stdio::null())+        .creation_flags(0x0800_0000) // CREATE_NO_WINDOW; never flash a console.+        .spawn()+    {+        Ok(child) => child,+        Err(error) => {+            // Application-control policies can allow a DLL while blocking a+            // child executable. Keep loading available with in-process Drop+            // cleanup only; a mapped image may remain after forced termination.+            // Helper integrity errors above still fail before any execution.+            tracing::warn!(path = %path.display(), helper = %helper.display(), %error,+                "native cleanup worker unavailable; temporary cleanup is best effort");+            return Ok(None);+        }+    };+    let mut input = child.stdin.take().unwrap();+    let bytes: Vec<_> = path+        .as_os_str()+        .encode_wide()+        .flat_map(u16::to_le_bytes)+        .collect();+    input.write_all(&bytes)?;+    tracing::debug!(path = %path.display(), helper_pid = child.id(), "armed native temporary cleanup");+    Ok(Some(Cleanup {+        _input: input,+        _child: child,+    }))+}

deps/swc/crates/swc_native_addon/src/cleanup/windows_helper.rs

new file mode 100644@@ -0,0 +1,46 @@+//! A private, single-file cleanup worker. EOF on the noninherited writer means+//! that the owning materialization was dropped or its process terminated.+//! Windows may release an image section slightly later, so deletion is retried.+#![windows_subsystem = "windows"]++use std::{+    ffi::OsString,+    fs,+    io::{self, Read},+    os::windows::ffi::OsStringExt,+    path::PathBuf,+    time::{Duration, Instant},+};++fn cleanup() -> io::Result<()> {+    let mut bytes = Vec::new();+    // Windows paths have at most 32,767 UTF-16 code units. Read one extra unit+    // to detect oversized input instead of deleting a truncated pathname.+    io::stdin().take(65_536).read_to_end(&mut bytes)?;+    if bytes.is_empty() || bytes.len() >= 65_536 || bytes.len() % 2 != 0 {+        return Err(io::ErrorKind::InvalidInput.into());+    }+    let wide: Vec<_> = bytes+        .chunks_exact(2)+        .map(|pair| u16::from_le_bytes([pair[0], pair[1]]))+        .collect();+    let path = PathBuf::from(OsString::from_wide(&wide));+    if !path.is_absolute() || wide.contains(&0) {+        return Err(io::ErrorKind::InvalidInput.into());+    }+    let deadline = Instant::now() + Duration::from_secs(10);+    loop {+        match fs::remove_file(&path) {+            Ok(()) => return Ok(()),+            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),+            Err(error) if Instant::now() >= deadline => return Err(error),+            Err(_) => std::thread::sleep(Duration::from_millis(25)),+        }+    }+}++fn main() {+    if cleanup().is_err() {+        std::process::exit(1);+    }+}

deps/swc/crates/swc_native_addon/src/error.rs

new file mode 100644@@ -0,0 +1,54 @@+use std::{fmt, io};++/// Stable categories used by the carrier's JavaScript errors.+#[derive(Clone, Copy, Debug, Eq, PartialEq)]+pub enum ErrorKind {+    Format,+    Integrity,+    Compression,+    Cache,+    Configuration,+    Load,+}++impl ErrorKind {+    pub fn code(self) -> &'static str {+        match self {+            Self::Format => "ERR_SWC_NATIVE_FORMAT",+            Self::Integrity => "ERR_SWC_NATIVE_INTEGRITY",+            Self::Compression => "ERR_SWC_NATIVE_COMPRESSION",+            Self::Cache => "ERR_SWC_NATIVE_CACHE",+            Self::Configuration => "ERR_SWC_NATIVE_CONFIGURATION",+            Self::Load => "ERR_SWC_NATIVE_LOAD",+        }+    }+}++#[derive(Debug)]+pub struct Error {+    pub kind: ErrorKind,+    message: String,+}++impl Error {+    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {+        Self {+            kind,+            message: message.into(),+        }+    }++    pub fn io(kind: ErrorKind, operation: &str, error: io::Error) -> Self {+        Self::new(kind, format!("{operation}: {error}"))+    }+}++impl fmt::Display for Error {+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+        self.message.fmt(f)+    }+}++impl std::error::Error for Error {}++pub type Result<T> = std::result::Result<T, Error>;

deps/swc/crates/swc_native_addon/src/format.rs

new file mode 100644@@ -0,0 +1,503 @@+//! Version 1 uses a 96-byte little-endian header followed by one zstd frame.+//! Integer fields are decoded explicitly: Rust layout and host byte order are+//! deliberately not part of the file format.++use std::io::{Cursor, Read, Seek, SeekFrom, Write};++use sha2::{Digest, Sha512};++use crate::{+    integrity::{RuntimeIntegrity, Verifier},+    Error, ErrorKind, Result,+};++pub const MAGIC: &[u8; 8] = b"SWCNZSTD";+pub const VERSION: u16 = 1;+pub const HEADER_LEN: usize = 96;+pub const MAX_SIZE: u64 = 2 * 1024 * 1024 * 1024;+pub const COMPRESSION_LEVEL: i32 = 16;++#[derive(Clone, Debug, Eq, PartialEq)]+pub struct Header {+    pub target: NativeTarget,+    pub compressed_len: u64,+    pub raw_len: u64,+    pub digest: [u8; 64],+}++impl Header {+    pub fn encode(&self) -> [u8; HEADER_LEN] {+        let mut bytes = [0; HEADER_LEN];+        bytes[..8].copy_from_slice(MAGIC);+        bytes[8..10].copy_from_slice(&VERSION.to_le_bytes());+        bytes[10..12].copy_from_slice(&(HEADER_LEN as u16).to_le_bytes());+        bytes[12] = self.target as u8;+        bytes[16..24].copy_from_slice(&self.compressed_len.to_le_bytes());+        bytes[24..32].copy_from_slice(&self.raw_len.to_le_bytes());+        bytes[32..].copy_from_slice(&self.digest);+        bytes+    }++    pub fn cache_key(&self) -> String {+        use std::fmt::Write;+        let mut key = String::with_capacity(128);+        for byte in self.digest {+            // Formatting into a String cannot fail.+            let _ = write!(key, "{byte:02x}");+        }+        key+    }++    /// Verify a complete raw file, including cache hits. Never trust a filename+    /// or an earlier successful verification as evidence about today's bytes.+    pub fn verify(&self, input: &mut (impl Read + Seek)) -> Result<()> {+        self.verify_with(input, Verifier::sha512(self))+    }++    fn verify_with(&self, input: &mut (impl Read + Seek), mut hash: Verifier) -> Result<()> {+        let len = input+            .seek(SeekFrom::End(0))+            .map_err(|e| Error::io(ErrorKind::Integrity, "measure raw addon", e))?;+        if len != self.raw_len {+            return Err(Error::new(+                ErrorKind::Integrity,+                "raw addon length mismatch",+            ));+        }+        native_kind(input, len)?;+        input+            .rewind()+            .map_err(|e| Error::io(ErrorKind::Integrity, "rewind raw addon", e))?;+        let mut buffer = vec![0; hash.buffer_len(self.raw_len)];+        let mut count = 0_u64;+        loop {+            let read = input+                .read(&mut buffer)+                .map_err(|e| Error::io(ErrorKind::Integrity, "hash raw addon", e))?;+            if read == 0 {+                break;+            }+            count += read as u64;+            if count > self.raw_len {+                return Err(Error::new(+                    ErrorKind::Integrity,+                    "cache file grew during verification",+                ));+            }+            hash.update(&buffer[..read]);+        }+        if count != self.raw_len {+            return Err(Error::new(+                ErrorKind::Integrity,+                "cache file shrank during verification",+            ));+        }+        hash.finish()+    }+}++/// Targets supported by the private carrier build.  The payload records this+/// explicitly because an ELF shared object does not encode whether it was+/// linked for the GNU or musl runtime.+#[derive(Clone, Copy, Debug, Eq, PartialEq)]+#[repr(u8)]+pub enum NativeTarget {+    X86_64AppleDarwin = 1,+    Aarch64AppleDarwin = 2,+    X86_64PcWindowsMsvc = 3,+    Aarch64PcWindowsMsvc = 4,+    X86_64UnknownLinuxGnu = 5,+    Aarch64UnknownLinuxGnu = 6,+    X86_64UnknownLinuxMusl = 7,+    Aarch64UnknownLinuxMusl = 8,+}++impl NativeTarget {+    pub fn host() -> Option<Self> {+        #[cfg(all(target_arch = "x86_64", target_os = "macos"))]+        return Some(Self::X86_64AppleDarwin);+        #[cfg(all(target_arch = "aarch64", target_os = "macos"))]+        return Some(Self::Aarch64AppleDarwin);+        #[cfg(all(target_arch = "x86_64", target_os = "windows"))]+        return Some(Self::X86_64PcWindowsMsvc);+        #[cfg(all(target_arch = "aarch64", target_os = "windows"))]+        return Some(Self::Aarch64PcWindowsMsvc);+        #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]+        return Some(Self::X86_64UnknownLinuxGnu);+        #[cfg(all(target_arch = "aarch64", target_os = "linux", target_env = "gnu"))]+        return Some(Self::Aarch64UnknownLinuxGnu);+        #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "musl"))]+        return Some(Self::X86_64UnknownLinuxMusl);+        #[cfg(all(target_arch = "aarch64", target_os = "linux", target_env = "musl"))]+        return Some(Self::Aarch64UnknownLinuxMusl);+        #[allow(unreachable_code)]+        None+    }++    pub fn as_triple(self) -> &'static str {+        match self {+            Self::X86_64AppleDarwin => "x86_64-apple-darwin",+            Self::Aarch64AppleDarwin => "aarch64-apple-darwin",+            Self::X86_64PcWindowsMsvc => "x86_64-pc-windows-msvc",+            Self::Aarch64PcWindowsMsvc => "aarch64-pc-windows-msvc",+            Self::X86_64UnknownLinuxGnu => "x86_64-unknown-linux-gnu",+            Self::Aarch64UnknownLinuxGnu => "aarch64-unknown-linux-gnu",+            Self::X86_64UnknownLinuxMusl => "x86_64-unknown-linux-musl",+            Self::Aarch64UnknownLinuxMusl => "aarch64-unknown-linux-musl",+        }+    }++    pub fn parse(value: &str) -> Result<Self> {+        match value {+            "x86_64-apple-darwin" => Ok(Self::X86_64AppleDarwin),+            "aarch64-apple-darwin" => Ok(Self::Aarch64AppleDarwin),+            "x86_64-pc-windows-msvc" => Ok(Self::X86_64PcWindowsMsvc),+            "aarch64-pc-windows-msvc" => Ok(Self::Aarch64PcWindowsMsvc),+            "x86_64-unknown-linux-gnu" => Ok(Self::X86_64UnknownLinuxGnu),+            "aarch64-unknown-linux-gnu" => Ok(Self::Aarch64UnknownLinuxGnu),+            "x86_64-unknown-linux-musl" => Ok(Self::X86_64UnknownLinuxMusl),+            "aarch64-unknown-linux-musl" => Ok(Self::Aarch64UnknownLinuxMusl),+            _ => Err(Error::new(+                ErrorKind::Format,+                format!("native carriers are not supported for {value}"),+            )),+        }+    }++    fn kind(self) -> NativeKind {+        match self {+            Self::X86_64AppleDarwin | Self::Aarch64AppleDarwin => NativeKind::MachO,+            Self::X86_64PcWindowsMsvc | Self::Aarch64PcWindowsMsvc => NativeKind::Pe,+            Self::X86_64UnknownLinuxGnu+            | Self::Aarch64UnknownLinuxGnu+            | Self::X86_64UnknownLinuxMusl+            | Self::Aarch64UnknownLinuxMusl => NativeKind::Elf,+        }+    }++    fn machine(self) -> u32 {+        match self {+            Self::X86_64AppleDarwin+            | Self::X86_64PcWindowsMsvc+            | Self::X86_64UnknownLinuxGnu+            | Self::X86_64UnknownLinuxMusl => 0x3e,+            Self::Aarch64AppleDarwin+            | Self::Aarch64PcWindowsMsvc+            | Self::Aarch64UnknownLinuxGnu+            | Self::Aarch64UnknownLinuxMusl => 0xb7,+        }+    }++    fn from_byte(value: u8) -> Option<Self> {+        Some(match value {+            1 => Self::X86_64AppleDarwin,+            2 => Self::Aarch64AppleDarwin,+            3 => Self::X86_64PcWindowsMsvc,+            4 => Self::Aarch64PcWindowsMsvc,+            5 => Self::X86_64UnknownLinuxGnu,+            6 => Self::Aarch64UnknownLinuxGnu,+            7 => Self::X86_64UnknownLinuxMusl,+            8 => Self::Aarch64UnknownLinuxMusl,+            _ => return None,+        })+    }+}++#[derive(Debug)]+pub struct Payload<'a> {+    pub header: Header,+    compressed: &'a [u8],+    integrity: Option<RuntimeIntegrity>,+}++impl<'a> Payload<'a> {+    pub fn parse(bytes: &'a [u8]) -> Result<Self> {+        let invalid = |message| Error::new(ErrorKind::Format, message);+        if bytes.len() < HEADER_LEN || &bytes[..8] != MAGIC {+            return Err(invalid("missing or truncated SWC native payload header"));+        }+        if bytes[8..10] != VERSION.to_le_bytes()+            || bytes[10..12] != (HEADER_LEN as u16).to_le_bytes()+            || bytes[13..16] != [0; 3]+        {+            return Err(invalid(+                "unsupported payload version, header size, or target flags",+            ));+        }+        let compressed_len = u64::from_le_bytes(bytes[16..24].try_into().unwrap());+        let raw_len = u64::from_le_bytes(bytes[24..32].try_into().unwrap());+        if compressed_len == 0 || raw_len == 0 || compressed_len > MAX_SIZE || raw_len > MAX_SIZE {+            return Err(invalid("payload lengths must be nonzero and at most 2 GiB"));+        }+        if compressed_len.checked_add(HEADER_LEN as u64) != Some(bytes.len() as u64) {+            return Err(invalid(+                "payload compressed length does not match its exact size",+            ));+        }+        let compressed = &bytes[HEADER_LEN..];+        // Reject skippable frames, concatenated frames, and trailing bytes before+        // consulting the cache. find_frame_compressed_size does not decompress.+        if !compressed.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) {+            return Err(invalid("payload is not an ordinary zstd frame"));+        }+        let frame_len = zstd::zstd_safe::find_frame_compressed_size(compressed)+            .map_err(|_| invalid("invalid or truncated zstd frame"))?;+        if frame_len != compressed.len() {+            return Err(invalid("payload must contain exactly one zstd frame"));+        }+        let content_len = zstd::zstd_safe::get_frame_content_size(compressed)+            .map_err(|_| invalid("invalid zstd content size"))?;+        if content_len != Some(raw_len) {+            return Err(invalid(+                "zstd content size must equal the declared raw length",+            ));+        }+        // The packer never uses dictionaries. The complete frame header is at+        // least six bytes after the ordinary magic has been checked above.+        if compressed+            .get(4)+            .map_or(true, |descriptor| descriptor & 3 != 0)+        {+            return Err(invalid("zstd dictionaries are not supported"));+        }+        Ok(Self {+            header: Header {+                target: NativeTarget::from_byte(bytes[12])+                    .ok_or_else(|| invalid("unsupported native carrier target"))?,+                compressed_len,+                raw_len,+                digest: bytes[32..96].try_into().unwrap(),+            },+            compressed,+            integrity: None,+        })+    }++    /// Opt into runtime BLAKE3 verification only after binding the complete+    /// payload header to the metadata retained by the trusted carrier build.+    /// `parse`, `Header::verify`, and ordinary decoding still default to+    /// SHA-512.+    pub fn with_integrity(mut self, integrity: RuntimeIntegrity) -> Result<Self> {+        integrity.verifier(&self.header)?;+        self.integrity = Some(integrity);+        Ok(self)+    }++    fn verifier(&self) -> Result<Verifier> {+        match &self.integrity {+            Some(integrity) => integrity.verifier(&self.header),+            None => Ok(Verifier::sha512(&self.header)),+        }+    }++    /// Read and verify the complete materialized image on every use, including+    /// cache hits and the readback after filesystem compression.+    pub fn verify_image(&self, input: &mut (impl Read + Seek)) -> Result<()> {+        self.header.verify_with(input, self.verifier()?)+    }++    /// Check that the decoded image agrees with the target stamped by the+    /// trusted packer before a carrier embeds or loads it.+    pub fn verify_target(&self, input: &mut (impl Read + Seek)) -> Result<()> {+        native_target(input, self.header.raw_len, self.header.target)+    }++    /// Decode into an empty staging file. A caller must discard the file on any+    /// error; publication is allowed only after this method succeeds.+    pub fn decode_into(&self, output: &mut (impl Read + Write + Seek)) -> Result<()> {+        let mut hash = self.verifier()?;+        let window_log = (64 - self.header.raw_len.saturating_sub(1).leading_zeros()).max(10);+        if self.compressed[4] & 0x20 == 0 {+            let descriptor = self.compressed[5];+            let base = 1_u64 << (10 + (descriptor >> 3));+            let window = base + (base / 8) * u64::from(descriptor & 7);+            if window > 1_u64 << window_log {+                return Err(Error::new(+                    ErrorKind::Compression,+                    "zstd window exceeds the bounded raw size",+                ));+            }+        }+        let mut decoder = zstd::stream::read::Decoder::with_buffer(self.compressed)+            .map_err(|e| Error::io(ErrorKind::Compression, "initialize zstd decoder", e))?+            .single_frame();+        // A small claimed output must not allocate a maliciously large window.+        // Also check the descriptor above: zstd's direct-output optimization can+        // bypass its configured window limit when the entire output fits at once.+        decoder+            .window_log_max(window_log)+            .map_err(|e| Error::io(ErrorKind::Compression, "limit zstd window", e))?;+        let mut count = 0_u64;+        let mut buffer = vec![0; hash.buffer_len(self.header.raw_len)];+        loop {+            let read = decoder+                .read(&mut buffer)+                .map_err(|e| Error::io(ErrorKind::Compression, "decompress native addon", e))?;+            if read == 0 {+                break;+            }+            count += read as u64;+            if count > self.header.raw_len {+                return Err(Error::new(+                    ErrorKind::Integrity,+                    "decoded addon exceeds declared length",+                ));+            }+            hash.update(&buffer[..read]);+            output+                .write_all(&buffer[..read])+                .map_err(|e| Error::io(ErrorKind::Cache, "write decoded addon", e))?;+        }+        if count != self.header.raw_len {+            return Err(Error::new(+                ErrorKind::Integrity,+                "decoded addon length mismatch",+            ));+        }+        hash.finish()?;+        native_kind(output, count)?;+        Ok(())+    }+}++/// Compress the final stripped addon, never a pre-strip intermediate.+pub fn pack(raw: &[u8], target: NativeTarget) -> Result<Vec<u8>> {+    if raw.is_empty() || raw.len() as u64 > MAX_SIZE {+        return Err(Error::new(+            ErrorKind::Format,+            "raw addon must be nonempty and at most 2 GiB",+        ));+    }+    native_target(&mut Cursor::new(raw), raw.len() as u64, target)?;+    let compressed = zstd::bulk::compress(raw, COMPRESSION_LEVEL)+        .map_err(|e| Error::io(ErrorKind::Compression, "compress native addon", e))?;+    if compressed.len() as u64 > MAX_SIZE {+        return Err(Error::new(+            ErrorKind::Format,+            "compressed addon exceeds 2 GiB",+        ));+    }+    let header = Header {+        target,+        compressed_len: compressed.len() as u64,+        raw_len: raw.len() as u64,+        digest: Sha512::digest(raw).into(),+    };+    let mut bytes = Vec::with_capacity(HEADER_LEN + compressed.len());+    bytes.extend_from_slice(&header.encode());+    bytes.extend_from_slice(&compressed);+    Ok(bytes)+}++#[derive(Clone, Copy, Debug, Eq, PartialEq)]+pub enum NativeKind {+    Elf,+    MachO,+    Pe,+}++/// Check magic and the minimum structure needed to avoid accepting arbitrary+/// data bearing only a short prefix. The OS loader validates the full image.+pub fn native_kind(input: &mut (impl Read + Seek), len: u64) -> Result<NativeKind> {+    let invalid = || {+        Error::new(+            ErrorKind::Format,+            "raw payload is not a Mach-O, ELF, or PE addon",+        )+    };+    input.rewind().map_err(|_| invalid())?;+    let mut prefix = [0; 64];+    if len < prefix.len() as u64 {+        return Err(invalid());+    }+    input.read_exact(&mut prefix).map_err(|_| invalid())?;+    if &prefix[..4] == b"\x7fELF"+        && matches!(prefix[4], 1 | 2)+        && matches!(prefix[5], 1 | 2)+        && prefix[6] == 1+    {+        return Ok(NativeKind::Elf);+    }+    if matches!(+        u32::from_be_bytes(prefix[..4].try_into().unwrap()),+        0xfeedface+            | 0xcefaedfe+            | 0xfeedfacf+            | 0xcffaedfe+            | 0xcafebabe+            | 0xbebafeca+            | 0xcafebabf+            | 0xbfbafeca+    ) {+        return Ok(NativeKind::MachO);+    }+    if &prefix[..2] == b"MZ" {+        let offset = u32::from_le_bytes(prefix[60..64].try_into().unwrap()) as u64;+        if offset < 64 || offset.checked_add(24).map_or(true, |end| end > len) {+            return Err(invalid());+        }+        input.seek(SeekFrom::Start(offset)).map_err(|_| invalid())?;+        let mut signature = [0; 4];+        input.read_exact(&mut signature).map_err(|_| invalid())?;+        if signature == *b"PE\0\0" {+            return Ok(NativeKind::Pe);+        }+    }+    Err(invalid())+}++/// Validate the executable format and machine type for a supported carrier.+/// Target metadata supplies the otherwise-unobservable Linux libc ABI.+pub fn native_target(input: &mut (impl Read + Seek), len: u64, target: NativeTarget) -> Result<()> {+    let invalid = || {+        Error::new(+            ErrorKind::Format,+            "raw addon does not match native carrier target",+        )+    };+    if native_kind(input, len)? != target.kind() {+        return Err(invalid());+    }+    input.rewind().map_err(|_| invalid())?;+    let mut prefix = [0; 64];+    input.read_exact(&mut prefix).map_err(|_| invalid())?;+    let machine = match target.kind() {+        NativeKind::Elf => {+            if prefix[4] != 2 || prefix[5] != 1 {+                return Err(invalid());+            }+            u32::from(u16::from_le_bytes(prefix[18..20].try_into().unwrap()))+        }+        NativeKind::MachO => {+            let magic = u32::from_le_bytes(prefix[..4].try_into().unwrap());+            let cpu = match magic {+                0xfeedface | 0xfeedfacf => u32::from_le_bytes(prefix[4..8].try_into().unwrap()),+                0xcefaedfe | 0xcffaedfe => u32::from_be_bytes(prefix[4..8].try_into().unwrap()),+                _ => return Err(invalid()),+            };+            match cpu {+                0x0100_0007 => 0x3e,+                0x0100_000c => 0xb7,+                _ => return Err(invalid()),+            }+        }+        NativeKind::Pe => {+            let offset = u32::from_le_bytes(prefix[60..64].try_into().unwrap()) as u64;+            input+                .seek(SeekFrom::Start(offset + 4))+                .map_err(|_| invalid())?;+            let mut bytes = [0; 2];+            input.read_exact(&mut bytes).map_err(|_| invalid())?;+            match u16::from_le_bytes(bytes) {+                0x8664 => 0x3e,+                0xaa64 => 0xb7,+                _ => return Err(invalid()),+            }+        }+    };+    if machine == target.machine() {+        Ok(())+    } else {+        Err(invalid())+    }+}

deps/swc/crates/swc_native_addon/src/integrity.rs

new file mode 100644@@ -0,0 +1,160 @@+//! Private carrier metadata, separate from the stable v1 payload format.+//!+//! The build and release verifier establish SHA-512 equality first. Runtime+//! verification uses BLAKE3 over every byte, bound to that complete v1 header.+//! Neither digest authenticates an untrusted carrier or replaces code signing.++use std::io::{Read, Seek};++use sha2::{Digest, Sha512};++use crate::{+    format::{Header, HEADER_LEN},+    Error, ErrorKind, Result,+};++pub const INTEGRITY_MAGIC: &[u8; 8] = b"SWCNB3V1";+pub const INTEGRITY_LEN: usize = 8 + HEADER_LEN + 32;++#[derive(Clone, Debug)]+pub struct RuntimeIntegrity {+    header: [u8; HEADER_LEN],+    digest: [u8; 32],+}++impl RuntimeIntegrity {+    /// Derive runtime metadata only from bytes verified against the SHA-512+    /// payload header. This runs during building, never on a cache hit.+    pub fn from_raw(header: &Header, raw: &mut (impl Read + Seek)) -> Result<Self> {+        header.verify(raw)?;+        raw.rewind()+            .map_err(|e| Error::io(ErrorKind::Integrity, "rewind verified raw addon", e))?;+        let mut hash = blake3::Hasher::new();+        let mut buffer = [0; 64 * 1024];+        loop {+            let len = raw+                .read(&mut buffer)+                .map_err(|e| Error::io(ErrorKind::Integrity, "hash verified raw addon", e))?;+            if len == 0 {+                break;+            }+            hash.update(&buffer[..len]);+        }+        Ok(Self {+            header: header.encode(),+            digest: *hash.finalize().as_bytes(),+        })+    }++    pub fn encode(&self) -> [u8; INTEGRITY_LEN] {+        let mut bytes = [0; INTEGRITY_LEN];+        bytes[..8].copy_from_slice(INTEGRITY_MAGIC);+        bytes[8..8 + HEADER_LEN].copy_from_slice(&self.header);+        bytes[8 + HEADER_LEN..].copy_from_slice(&self.digest);+        bytes+    }++    pub fn parse(bytes: &[u8]) -> Result<Self> {+        if bytes.len() != INTEGRITY_LEN || &bytes[..8] != INTEGRITY_MAGIC {+            return Err(Error::new(+                ErrorKind::Integrity,+                "invalid native runtime integrity metadata",+            ));+        }+        Ok(Self {+            header: bytes[8..8 + HEADER_LEN].try_into().unwrap(),+            digest: bytes[8 + HEADER_LEN..].try_into().unwrap(),+        })+    }++    pub(crate) fn verifier(&self, header: &Header) -> Result<Verifier> {+        if self.header != header.encode() {+            return Err(Error::new(+                ErrorKind::Integrity,+                "runtime integrity metadata does not match payload header",+            ));+        }+        Ok(Verifier::Blake3(Box::default(), self.digest))+    }+}++/// Select a digest once per verification, with no persistent validation cache.+pub(crate) enum Verifier {+    Sha512(Box<Sha512>, [u8; 64]),+    Blake3(Box<blake3::Hasher>, [u8; 32]),+}++impl Verifier {+    pub(crate) fn sha512(header: &Header) -> Self {+        Self::Sha512(Box::default(), header.digest)+    }++    pub(crate) fn buffer_len(&self, _raw_len: u64) -> usize {+        // Enter the worker pool once for ordinary addon sizes. Repeated 1 MiB+        // dispatches stall Rosetta verification on constrained CI hosts. Bound+        // scratch memory even for the largest accepted payloads; the buffer is+        // freed after verification. Small images and SHA-512 keep streaming.+        #[cfg(all(target_os = "macos", target_arch = "x86_64"))]+        if matches!(self, Self::Blake3(..)) && _raw_len >= 1024 * 1024 {+            return _raw_len.min(32 * 1024 * 1024) as usize;+        }+        64 * 1024+    }++    pub(crate) fn update(&mut self, bytes: &[u8]) {+        match self {+            Self::Sha512(hash, _) => hash.update(bytes),+            Self::Blake3(hash, _) => {+                #[cfg(all(target_os = "macos", target_arch = "x86_64"))]+                if bytes.len() >= 128 * 1024 {+                    if let Some(pool) = verification_pool() {+                        pool.install(|| hash.update_rayon(bytes));+                        return;+                    }+                }+                hash.update(bytes);+            }+        }+    }++    pub(crate) fn finish(self) -> Result<()> {+        let algorithm = match self {+            Self::Sha512(hash, expected) => {+                if hash.finalize()[..] == expected {+                    return Ok(());+                }+                "SHA-512"+            }+            Self::Blake3(hash, expected) => {+                if hash.finalize().as_bytes() == &expected {+                    return Ok(());+                }+                "BLAKE3"+            }+        };+        Err(Error::new(+            ErrorKind::Integrity,+            format!("raw addon {algorithm} mismatch"),+        ))+    }+}++/// Bound Rosetta verification work without using the application's global+/// Rayon pool. Only worker threads are reused: every verification still reads+/// and hashes every file byte. Resource-constrained hosts hash sequentially.+#[cfg(all(target_os = "macos", target_arch = "x86_64"))]+fn verification_pool() -> Option<&'static rayon_core::ThreadPool> {+    static POOL: std::sync::OnceLock<Option<rayon_core::ThreadPool>> = std::sync::OnceLock::new();+    POOL.get_or_init(|| {+        let threads = std::thread::available_parallelism().ok()?.get().min(4);+        if threads < 2 {+            return None;+        }+        rayon_core::ThreadPoolBuilder::new()+            .num_threads(threads)+            .thread_name(|index| format!("swc-native-verify-{index}"))+            .build()+            .ok()+    })+    .as_ref()+}

deps/swc/crates/swc_native_addon/src/lib.rs

new file mode 100644@@ -0,0 +1,16 @@+//! Private wire format and verified materialization for SWC's native carriers.+//!+//! A digest establishes equality with the embedded addon, not publisher+//! identity. The installed carrier must already be trusted like any other+//! native addon.++pub mod cache;+mod error;+pub mod format;+pub mod integrity;+pub mod platform;+pub mod replacement;++pub use error::{Error, ErrorKind, Result};+#[cfg(windows)]+mod cleanup;

deps/swc/crates/swc_native_addon/src/platform/mod.rs

new file mode 100644@@ -0,0 +1,28 @@+//! Small OS adapters. Cache coordination is scoped to file handles so process+//! death releases locks without PID files, stale-lock timeouts, or polling.++#[cfg(unix)]+mod unix;+#[cfg(windows)]+mod windows;++#[cfg(unix)]+pub use unix::*;+#[cfg(windows)]+pub use windows::*;++/// Keep tempfile's exclusive randomized naming, but establish the explicit+/// user owner and private DACL at creation on Windows, including elevated+/// tokens.+pub(crate) fn temporary_file(+    directory: &std::path::Path,+    prefix: &str,+    suffix: &str,+) -> std::io::Result<tempfile::NamedTempFile> {+    let mut builder = tempfile::Builder::new();+    builder.prefix(prefix).suffix(suffix);+    #[cfg(windows)]+    return builder.make_in(directory, new_private_file);+    #[cfg(unix)]+    builder.tempfile_in(directory)+}

deps/swc/crates/swc_native_addon/src/platform/unix.rs

new file mode 100644@@ -0,0 +1,400 @@+use std::{+    env,+    ffi::CStr,+    fs::{self, File, OpenOptions},+    io,+    os::unix::{+        ffi::OsStrExt,+        fs::{DirBuilderExt, MetadataExt, OpenOptionsExt},+    },+    path::{Path, PathBuf},+};++use crate::{Error, ErrorKind, Result};++#[cfg(target_os = "macos")]+const STICKY_BIT: u32 = libc::S_ISVTX as u32;+#[cfg(not(target_os = "macos"))]+const STICKY_BIT: u32 = libc::S_ISVTX;++pub fn user_namespace() -> Result<String> {+    // The effective identity, not a user-controlled environment variable, owns+    // both the cache and the bytes subsequently passed to dlopen.+    Ok(format!("swc-native-{}", unsafe { libc::geteuid() }))+}++/// Prefer a per-user location: hardened Linux installations commonly mount the+/// system temporary directory `noexec`, preventing native addon loading. The+/// temporary directory is a fallback only and must pass the same checks.+pub fn user_cache_root() -> Result<PathBuf> {+    let root = env::var_os("XDG_CACHE_HOME")+        .filter(|value| Path::new(value).is_absolute())+        .or_else(|| {+            env::var_os("HOME").map(|home| {+                if cfg!(target_os = "macos") {+                    PathBuf::from(home).join("Library/Caches")+                } else {+                    PathBuf::from(home).join(".cache")+                }+                .into_os_string()+            })+        })+        .map(PathBuf::from)+        .ok_or_else(|| Error::new(ErrorKind::Cache, "cannot determine native addon user cache"))?;+    executable_cache_root(&root)?;+    Ok(root)+}++/// Resolve aliases such as macOS's /var before validating fallback ancestors.+/// The caller still checks mount execution policy and directory security.+pub(crate) fn fallback_cache_root() -> Result<PathBuf> {+    fs::canonicalize(env::temp_dir())+        .map_err(|e| Error::io(ErrorKind::Cache, "resolve native addon fallback cache", e))+}++/// Reject roots from which the dynamic loader cannot map a materialized addon.+/// This applies to explicit custom roots as well as the user default.+pub fn executable_cache_root(root: &Path) -> Result<()> {+    if noexec_mount(root)? {+        return Err(Error::new(+            ErrorKind::Cache,+            format!(+                "native addon user cache {} is mounted noexec",+                root.display(),+            ),+        ));+    }+    Ok(())+}++/// A cache root and every ancestor must be stable after validation.  A+/// world-writable sticky directory such as /tmp is safe only when it belongs+/// to the effective user or root. A sticky directory owned by another user+/// still lets that owner rename our private namespace after validation.+/// Trusted environments may explicitly skip this ancestor validation with+/// `SWC_NATIVE_BINDING_CACHE_SKIP_SECURITY_CHECK=1`. Absolute paths, private+/// cache entries, and executable-mount requirements are still enforced.+pub fn secure_cache_root(root: &Path) -> io::Result<()> {+    if !root.is_absolute() {+        return Err(io::Error::new(+            io::ErrorKind::InvalidInput,+            "cache root must be absolute",+        ));+    }+    // Some isolated CI executors cannot satisfy ancestor ownership or mode+    // requirements. This opt-out also permits symlinked ancestors, so callers+    // must trust every component against replacement before the native load.+    if env::var_os("SWC_NATIVE_BINDING_CACHE_SKIP_SECURITY_CHECK").as_deref()+        == Some(std::ffi::OsStr::new("1"))+    {+        tracing::debug!(path = %root.display(), "skipping native cache root security check by explicit environment opt-out");+        return Ok(());+    }+    for directory in root.ancestors() {+        let metadata = fs::symlink_metadata(directory)?;+        if !metadata.is_dir() || metadata.file_type().is_symlink() {+            return Err(io::Error::new(+                io::ErrorKind::PermissionDenied,+                "cache root contains a non-directory or symlink",+            ));+        }+        if !cache_ancestor_is_secure(metadata.mode(), metadata.uid(), unsafe { libc::geteuid() }) {+            return Err(io::Error::new(+                io::ErrorKind::PermissionDenied,+                "cache root has a parent writable by another user without trusted sticky \+                 protection",+            ));+        }+    }+    Ok(())+}++fn cache_ancestor_is_secure(mode: u32, owner: u32, current: u32) -> bool {+    if owner != current && owner != 0 {+        // Even a mode-0555 directory owner can chmod it, rename our validated+        // namespace, and replace the addon before dlopen reopens its path.+        return false;+    }+    mode & 0o022 == 0 || mode & STICKY_BIT != 0+}++#[cfg(target_os = "linux")]+fn noexec_mount(path: &Path) -> Result<bool> {+    let mut existing = path;+    while !existing.exists() {+        existing = existing+            .parent()+            .ok_or_else(|| Error::new(ErrorKind::Cache, "cache root has no existing ancestor"))?;+    }+    let path = fs::canonicalize(existing)+        .map_err(|e| Error::io(ErrorKind::Cache, "resolve native addon cache", e))?;+    let mounts = fs::read_to_string("/proc/self/mountinfo")+        .map_err(|e| Error::io(ErrorKind::Cache, "read Linux mount table", e))?;+    Ok(noexec_mount_in(&path, &mounts))+}++#[cfg(target_os = "linux")]+fn noexec_mount_in(path: &Path, mounts: &str) -> bool {+    let mut selected: Option<(PathBuf, bool)> = None;+    for line in mounts.lines() {+        let Some((before, _)) = line.split_once(" - ") else {+            continue;+        };+        let fields: Vec<_> = before.split_whitespace().collect();+        if fields.len() < 6 {+            continue;+        }+        let mount = PathBuf::from(fields[4].replace("\\040", " ").replace("\\011", "\t"));+        if path.starts_with(&mount)+            && selected.as_ref().map_or(true, |(current, _)| {+                mount.as_os_str().len() > current.as_os_str().len()+            })+        {+            selected = Some((mount, fields[5].split(',').any(|option| option == "noexec")));+        }+    }+    selected.is_some_and(|(_, noexec)| noexec)+}++#[cfg(not(target_os = "linux"))]+#[cfg(not(target_os = "macos"))]+fn noexec_mount(_path: &Path) -> Result<bool> {+    Ok(false)+}++#[cfg(target_os = "macos")]+fn noexec_mount(path: &Path) -> Result<bool> {+    use std::os::fd::AsRawFd;++    let mut existing = path;+    while !existing.exists() {+        existing = existing+            .parent()+            .ok_or_else(|| Error::new(ErrorKind::Cache, "cache root has no existing ancestor"))?;+    }+    let file = File::open(existing)+        .map_err(|e| Error::io(ErrorKind::Cache, "open native addon cache mount", e))?;+    let mut stat: libc::statfs = unsafe { std::mem::zeroed() };+    if unsafe { libc::fstatfs(file.as_raw_fd(), &mut stat) } != 0 {+        return Err(Error::io(+            ErrorKind::Cache,+            "inspect native addon cache mount",+            io::Error::last_os_error(),+        ));+    }+    Ok(mount_is_noexec(stat.f_flags))+}++#[cfg(target_os = "macos")]+fn mount_is_noexec(flags: u32) -> bool {+    flags & libc::MNT_NOEXEC as u32 != 0+}++pub fn private_directory(path: &Path) -> io::Result<()> {+    match fs::DirBuilder::new().mode(0o700).create(path) {+        Ok(()) => {}+        Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}+        Err(e) => return Err(e),+    }+    let metadata = fs::symlink_metadata(path)?;+    if !metadata.is_dir()+        || metadata.uid() != unsafe { libc::geteuid() }+        || metadata.mode() & 0o077 != 0+    {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache directory must be a real directory owned by the current user with mode 0700",+        ));+    }+    Ok(())+}++/// Create cache-root components with owner-only permissions even when the+/// caller has selected a permissive umask.+pub fn create_cache_root(path: &Path) -> io::Result<()> {+    fs::DirBuilder::new()+        .recursive(true)+        .mode(0o700)+        .create(path)+}++pub fn open_regular(path: &Path, write: bool, create: bool) -> io::Result<File> {+    let file = OpenOptions::new()+        .read(true)+        .write(write)+        .create(create)+        .mode(0o600)+        .custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK)+        .open(path)?;+    let metadata = file.metadata()?;+    if !metadata.is_file()+        || metadata.uid() != unsafe { libc::geteuid() }+        || metadata.mode() & 0o022 != 0+        || metadata.nlink() != 1+    {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache entry must be a private, regular, singly linked file",+        ));+    }+    Ok(file)+}++pub fn lock_exclusive(file: &File) -> io::Result<()> {+    use std::os::fd::AsRawFd;+    loop {+        if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) } == 0 {+            return Ok(());+        }+        let error = io::Error::last_os_error();+        if error.kind() != io::ErrorKind::Interrupted {+            return Err(error);+        }+    }+}++/// Try to acquire an exclusive lock without waiting for an active loader.+pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {+    use std::os::fd::AsRawFd;++    loop {+        if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } == 0 {+            return Ok(true);+        }+        let error = io::Error::last_os_error();+        if error.kind() == io::ErrorKind::WouldBlock {+            return Ok(false);+        }+        if error.kind() != io::ErrorKind::Interrupted {+            return Err(error);+        }+    }+}++/// An ACL that is not equivalent to ordinary mode bits would be lost by the+/// staging-file replacement path. Callers must skip replacement in that case.+#[cfg(target_os = "macos")]+pub fn has_extended_acl(file: &File) -> io::Result<bool> {+    use std::os::fd::AsRawFd;++    extern "C" {+        fn acl_get_fd(fd: libc::c_int) -> *mut libc::c_void;+        fn acl_free(object: *mut libc::c_void) -> libc::c_int;+    }++    let acl = unsafe { acl_get_fd(file.as_raw_fd()) };+    if acl.is_null() {+        let error = io::Error::last_os_error();+        // Darwin reports an ordinary mode-only file as ENOENT rather than+        // returning an empty ACL object.+        return if error.raw_os_error() == Some(libc::ENOENT) {+            Ok(false)+        } else {+            Err(error)+        };+    }+    if unsafe { acl_free(acl) } != 0 {+        return Err(io::Error::last_os_error());+    }+    Ok(true)+}++pub fn sync_directory(path: &Path) -> io::Result<()> {+    File::open(path)?.sync_all()+}++/// Publish a same-filesystem staged file over its final pathname.+pub fn replace_file(source: &Path, destination: &Path) -> io::Result<()> {+    fs::rename(source, destination)+}++/// Preserve installation permissions, group ownership, and security metadata+/// before replacing an image. Compression forks describe the carrier's bytes,+/// so the new raw image must regenerate those rather than inherit them.+pub fn copy_metadata(source: &File, destination: &File) -> io::Result<()> {+    use std::os::fd::AsRawFd;++    use xattr::FileExt;+    let metadata = source.metadata()?;+    if unsafe { libc::fchown(destination.as_raw_fd(), metadata.uid(), metadata.gid()) } != 0 {+        return Err(io::Error::last_os_error());+    }+    destination.set_permissions(metadata.permissions())?;+    for name in source.list_xattr()? {+        if name == "com.apple.decmpfs" || name == "com.apple.ResourceFork" {+            continue;+        }+        if let Some(value) = source.get_xattr(&name)? {+            // Security labels can be inherited correctly while setting even+            // that same value is forbidden by the host policy. Avoid needless+            // changes, but fail the optimization if a differing label cannot+            // be preserved.+            if destination.get_xattr(&name)?.as_deref() != Some(value.as_slice()) {+                destination.set_xattr(&name, &value)?;+            }+        }+    }+    Ok(())+}++/// Resolve an address within the mapped carrier, even after its directory+/// entry has been atomically replaced. No N-API path API is needed.+///+/// # Safety+/// `address` must point into a currently loaded native image.+pub unsafe fn carrier_path(address: *const std::ffi::c_void) -> Result<PathBuf> {+    let mut info: libc::Dl_info = std::mem::zeroed();+    if libc::dladdr(address, &mut info) == 0 || info.dli_fname.is_null() {+        return Err(Error::new(+            ErrorKind::Load,+            "dladdr could not locate the native carrier",+        ));+    }+    let name = CStr::from_ptr(info.dli_fname).to_bytes();+    let path = Path::new(std::ffi::OsStr::from_bytes(name));+    fs::canonicalize(path).map_err(|e| {+        Error::io(+            ErrorKind::Load,+            &format!("resolve carrier {}", path.display()),+            e,+        )+    })+}++pub(crate) fn prepare_cache_image(_file: &File) -> io::Result<()> {+    Ok(())+}++#[cfg(all(test, target_os = "linux"))]+mod tests {+    use std::path::Path;++    use super::{cache_ancestor_is_secure, noexec_mount_in};++    #[test]+    fn selects_the_most_specific_mount_option() {+        let mounts = "24 1 0:20 / / rw,relatime - ext4 /dev/root rw\n36 24 0:31 / /custom \+                      rw,noexec - tmpfs tmpfs rw\n";+        assert!(noexec_mount_in(Path::new("/custom/cache"), mounts));+        assert!(!noexec_mount_in(Path::new("/other/cache"), mounts));+    }++    #[test]+    fn rejects_sticky_writable_directory_owned_by_another_user() {+        assert!(cache_ancestor_is_secure(0o1777, 0, 1000));+        assert!(cache_ancestor_is_secure(0o1777, 1000, 1000));+        assert!(!cache_ancestor_is_secure(0o1777, 1001, 1000));+        assert!(!cache_ancestor_is_secure(0o0755, 1001, 1000));+        assert!(!cache_ancestor_is_secure(0o0555, 1001, 1000));+        assert!(!cache_ancestor_is_secure(0o0777, 1000, 1000));+    }+}++#[cfg(all(test, target_os = "macos"))]+mod macos_tests {+    #[test]+    fn recognizes_noexec_mount_flags() {+        assert!(super::mount_is_noexec(libc::MNT_NOEXEC as u32));+        assert!(!super::mount_is_noexec(0));+    }+}

deps/swc/crates/swc_native_addon/src/platform/windows.rs

new file mode 100644@@ -0,0 +1,671 @@+use std::{+    env,+    ffi::{c_void, OsString},+    fs::{self, File, OpenOptions},+    io,+    os::windows::{+        ffi::{OsStrExt, OsStringExt},+        fs::{MetadataExt, OpenOptionsExt},+    },+    path::{Path, PathBuf},+    ptr,+};++use windows_sys::Win32::{+    Foundation::{CloseHandle, LocalFree, ERROR_ALREADY_EXISTS, ERROR_LOCK_VIOLATION, HANDLE},+    Security::{+        Authorization::{+            ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW,+            GetNamedSecurityInfoW, GetSecurityInfo, SDDL_REVISION_1, SE_FILE_OBJECT,+        },+        GetAce, GetTokenInformation, TokenUser, ACL, OWNER_SECURITY_INFORMATION,+        SECURITY_ATTRIBUTES, TOKEN_QUERY, TOKEN_USER,+    },+    Storage::FileSystem::{+        CreateDirectoryW, GetFileInformationByHandle, LockFileEx, MoveFileExW,+        BY_HANDLE_FILE_INFORMATION, COMPRESSION_FORMAT_NONE, FILE_ATTRIBUTE_COMPRESSED,+        FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE,+        FILE_SHARE_READ, FILE_SHARE_WRITE, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,+        MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,+    },+    System::{+        Ioctl::FSCTL_SET_COMPRESSION,+        LibraryLoader::{+            GetModuleFileNameW, GetModuleHandleExW, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,+            GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,+        },+        Threading::{GetCurrentProcess, OpenProcessToken},+        IO::{DeviceIoControl, OVERLAPPED},+    },+};++use crate::{Error, ErrorKind, Result};++const SYSTEM_SID: &str = "S-1-5-18";+const ADMINISTRATORS_SID: &str = "S-1-5-32-544";+// Windows system directories may be owned by its servicing account. This trust+// applies only to ancestors: cache directories/files still require our own SID.+const TRUSTED_INSTALLER_SID: &str =+    "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464";+const INHERIT_ONLY_ACE: u8 = 0x08;+const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;+const ACCESS_DENIED_ACE_TYPE: u8 = 1;+const DANGEROUS_DIRECTORY_ACCESS: u32 =+    0x0000_0040 | 0x0001_0000 | 0x0004_0000 | 0x0008_0000 | 0x1000_0000 | 0x4000_0000;++fn wide(path: &Path) -> Vec<u16> {+    path.as_os_str().encode_wide().chain(Some(0)).collect()+}++struct OwnedHandle(HANDLE);+impl Drop for OwnedHandle {+    fn drop(&mut self) {+        unsafe {+            CloseHandle(self.0);+        }+    }+}++struct LocalAllocation(*mut c_void);+impl Drop for LocalAllocation {+    fn drop(&mut self) {+        unsafe {+            LocalFree(self.0);+        }+    }+}++fn sid_string(sid: *mut c_void) -> io::Result<String> {+    let mut text = ptr::null_mut();+    if unsafe { ConvertSidToStringSidW(sid, &mut text) } == 0 {+        return Err(io::Error::last_os_error());+    }+    let _allocation = LocalAllocation(text.cast());+    let mut len = 0;+    unsafe {+        while *text.add(len) != 0 {+            len += 1;+        }+        Ok(String::from_utf16_lossy(std::slice::from_raw_parts(+            text, len,+        )))+    }+}++fn current_sid() -> io::Result<String> {+    let mut token = 0;+    if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {+        return Err(io::Error::last_os_error());+    }+    let token = OwnedHandle(token);+    let mut size = 0;+    unsafe {+        GetTokenInformation(token.0, TokenUser, ptr::null_mut(), 0, &mut size);+    }+    // TOKEN_USER contains pointers and needs pointer alignment, not Vec<u8>'s+    // nominal alignment. The trailing SID remains inside this allocation.+    let mut storage = vec![0_usize; (size as usize).div_ceil(std::mem::size_of::<usize>())];+    if unsafe {+        GetTokenInformation(+            token.0,+            TokenUser,+            storage.as_mut_ptr().cast(),+            size,+            &mut size,+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    sid_string(unsafe { (*(storage.as_ptr().cast::<TOKEN_USER>())).User.Sid })+}++pub fn user_namespace() -> Result<String> {+    current_sid()+        .map(|sid| format!("swc-native-{sid}"))+        .map_err(|e| Error::io(ErrorKind::Cache, "identify cache owner", e))+}++pub fn user_cache_root() -> Result<PathBuf> {+    env::var_os("LOCALAPPDATA")+        .filter(|value| Path::new(value).is_absolute())+        .map(|value| PathBuf::from(value).join("swc"))+        .ok_or_else(|| {+            Error::new(+                ErrorKind::Cache,+                "cannot determine native addon user cache: LOCALAPPDATA must be an absolute \+                 directory",+            )+        })+}++/// AppContainer grants inherited by LocalAppData can make it unsafe for native+/// loading. Use a separately validated profile cache rather than trusting those+/// grants or changing the user's directory ACLs.+pub(crate) fn fallback_cache_root() -> Result<PathBuf> {+    env::var_os("USERPROFILE")+        .filter(|value| Path::new(value).is_absolute())+        .map(|value| PathBuf::from(value).join(".swc-cache"))+        .ok_or_else(|| {+            Error::new(+                ErrorKind::Cache,+                "cannot determine native addon profile cache: USERPROFILE must be an absolute \+                 directory",+            )+        })+}++/// Windows does not expose a mount-level `noexec` equivalent for ordinary+/// addon cache paths. Filesystem access is validated during cache creation.+pub fn executable_cache_root(_root: &Path) -> Result<()> {+    Ok(())+}++pub fn secure_cache_root(root: &Path) -> io::Result<()> {+    if !root.is_absolute() {+        return Err(io::Error::new(+            io::ErrorKind::InvalidInput,+            "cache root must be absolute",+        ));+    }+    let sid = current_sid()?;+    // A reparse point or an untrusted owner/DACL in any component can redirect+    // or replace the validated cache namespace before LoadLibrary reopens it.+    for directory in root.ancestors() {+        validate_cache_ancestor(directory, &sid)?;+    }+    Ok(())+}++fn validate_cache_ancestor(path: &Path, current_sid: &str) -> io::Result<()> {+    let metadata = fs::symlink_metadata(path)?;+    if !metadata.is_dir() || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            format!(+                "cache ancestor {} is a non-directory or reparse point",+                path.display()+            ),+        ));+    }++    use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION;++    let mut owner = ptr::null_mut();+    let mut dacl = ptr::null_mut();+    let mut security = ptr::null_mut();+    let status = unsafe {+        GetNamedSecurityInfoW(+            wide(path).as_ptr(),+            SE_FILE_OBJECT,+            OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,+            &mut owner,+            ptr::null_mut(),+            &mut dacl,+            ptr::null_mut(),+            &mut security,+        )+    };+    if status != 0 {+        return Err(io::Error::from_raw_os_error(status as i32));+    }+    let _security = LocalAllocation(security);+    let owner = sid_string(owner)?;+    validate_ancestor_security(path, &owner, dacl, current_sid)+}++fn validate_ancestor_security(+    path: &Path,+    owner: &str,+    dacl: *mut ACL,+    current_sid: &str,+) -> io::Result<()> {+    if !trusted_principal(owner, current_sid) {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            format!(+                "cache ancestor {} has untrusted owner SID {owner}",+                path.display()+            ),+        ));+    }+    validate_ancestor_dacl(dacl, current_sid).map_err(|error| {+        io::Error::new(+            error.kind(),+            format!("cache ancestor {}: {error}", path.display()),+        )+    })+}++fn trusted_principal(sid: &str, current_sid: &str) -> bool {+    sid == current_sid+        || sid == SYSTEM_SID+        || sid == ADMINISTRATORS_SID+        || sid == TRUSTED_INSTALLER_SID+}++/// Conservatively reject ACLs that grant an untrusted principal the rights to+/// remove, retake ownership of, or rewrite a cache-root ancestor. This avoids+/// mutating caller-owned roots while preventing pathname substitution.+fn validate_ancestor_dacl(dacl: *mut ACL, current_sid: &str) -> io::Result<()> {+    let denied = |message| io::Error::new(io::ErrorKind::PermissionDenied, message);+    if dacl.is_null() {+        // A null DACL grants full access to every account.+        return Err(denied(+            "null DACL grants replacement to everyone".to_owned(),+        ));+    }+    for index in 0..u32::from(unsafe { (*dacl).AceCount }) {+        let mut ace = ptr::null_mut();+        if unsafe { GetAce(dacl, index, &mut ace) } == 0 {+            return Err(io::Error::last_os_error());+        }+        let flags = unsafe { *ace.cast::<u8>().add(1) };+        // An inheritance-only grant applies to children, not this directory.+        // Its inherited effective copy is checked when visiting that child.+        if flags & INHERIT_ONLY_ACE != 0 {+            continue;+        }+        let ace_type = unsafe { *ace.cast::<u8>() };+        if ace_type == ACCESS_DENIED_ACE_TYPE {+            continue;+        }+        if ace_type != ACCESS_ALLOWED_ACE_TYPE {+            // Object and callback allow ACEs use a different SID offset. Do+            // not risk misclassifying an untrusted grant as harmless.+            return Err(denied(format!("unsupported effective ACE type {ace_type}")));+        }+        let mask = unsafe { std::ptr::read_unaligned(ace.cast::<u8>().add(4).cast::<u32>()) };+        if mask & DANGEROUS_DIRECTORY_ACCESS == 0 {+            continue;+        }+        // ACCESS_ALLOWED_ACE stores the SID immediately after its header and+        // access mask (eight bytes in total).+        let trustee = sid_string(unsafe { ace.cast::<u8>().add(8).cast() })?;+        if !trusted_principal(&trustee, current_sid) {+            return Err(denied(format!(+                "DACL grants replacement rights {mask:#x} to SID {trustee}"+            )));+        }+    }+    Ok(())+}++pub fn private_directory(path: &Path) -> io::Result<()> {+    let sid = current_sid()?;+    let sddl: Vec<u16> = format!("O:{sid}D:P(A;;FA;;;{sid})(A;;FA;;;SY)")+        .encode_utf16()+        .chain(Some(0))+        .collect();+    let mut descriptor = ptr::null_mut();+    if unsafe {+        ConvertStringSecurityDescriptorToSecurityDescriptorW(+            sddl.as_ptr(),+            SDDL_REVISION_1,+            &mut descriptor,+            ptr::null_mut(),+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    let _descriptor = LocalAllocation(descriptor);+    let attributes = SECURITY_ATTRIBUTES {+        nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,+        lpSecurityDescriptor: descriptor,+        bInheritHandle: 0,+    };+    if unsafe { CreateDirectoryW(wide(path).as_ptr(), &attributes) } == 0 {+        let error = io::Error::last_os_error();+        if error.raw_os_error() != Some(ERROR_ALREADY_EXISTS as i32) {+            return Err(error);+        }+    }+    let metadata = fs::symlink_metadata(path)?;+    if !metadata.is_dir() || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache directory must not be a reparse point",+        ));+    }+    let mut owner = ptr::null_mut();+    let mut security = ptr::null_mut();+    let status = unsafe {+        GetNamedSecurityInfoW(+            wide(path).as_ptr(),+            SE_FILE_OBJECT,+            OWNER_SECURITY_INFORMATION,+            &mut owner,+            ptr::null_mut(),+            ptr::null_mut(),+            ptr::null_mut(),+            &mut security,+        )+    };+    if status != 0 {+        return Err(io::Error::from_raw_os_error(status as i32));+    }+    let _security = LocalAllocation(security);+    if sid_string(owner)? != sid {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache directory belongs to another user",+        ));+    }+    // An existing user-owned directory may have inherited a permissive DACL.+    // Restore the protected owner/SYSTEM DACL before using any cache entries.+    use windows_sys::Win32::Security::{+        SetFileSecurityW, DACL_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,+    };+    if unsafe {+        SetFileSecurityW(+            wide(path).as_ptr(),+            DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,+            descriptor,+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    Ok(())+}++/// An elevated token can default new files to the Administrators owner. Never+/// weaken validation to accept that owner: create our own files with the user's+/// SID explicitly, and leave existing files subject to the original checks.+pub(crate) fn new_private_file(path: &Path) -> io::Result<File> {+    use std::os::windows::io::FromRawHandle;++    use windows_sys::Win32::{+        Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE},+        Storage::FileSystem::{CreateFileW, CREATE_NEW, FILE_ATTRIBUTE_NORMAL},+    };+    let sid = current_sid()?;+    let sddl: Vec<u16> = format!("O:{sid}D:P(A;;FA;;;{sid})(A;;FA;;;SY)")+        .encode_utf16()+        .chain(Some(0))+        .collect();+    let mut descriptor = ptr::null_mut();+    if unsafe {+        ConvertStringSecurityDescriptorToSecurityDescriptorW(+            sddl.as_ptr(),+            SDDL_REVISION_1,+            &mut descriptor,+            ptr::null_mut(),+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    let _descriptor = LocalAllocation(descriptor);+    let attributes = SECURITY_ATTRIBUTES {+        nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,+        lpSecurityDescriptor: descriptor,+        bInheritHandle: 0,+    };+    let handle = unsafe {+        CreateFileW(+            wide(path).as_ptr(),+            GENERIC_READ | GENERIC_WRITE,+            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,+            &attributes,+            CREATE_NEW,+            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,+            0,+        )+    };+    if handle == INVALID_HANDLE_VALUE {+        return Err(io::Error::last_os_error());+    }+    Ok(unsafe { File::from_raw_handle(handle as _) })+}++pub fn open_regular(path: &Path, write: bool, create: bool) -> io::Result<File> {+    use std::os::windows::io::AsRawHandle;+    let open = || {+        OpenOptions::new()+            .read(true)+            .write(write)+            .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)+            .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)+            .open(path)+    };+    let file = if create {+        match new_private_file(path) {+            Ok(file) => file,+            Err(error) if error.kind() == io::ErrorKind::AlreadyExists => open()?,+            Err(error) => return Err(error),+        }+    } else {+        open()?+    };+    let metadata = file.metadata()?;+    if !metadata.is_file() || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache entry must be a regular file without a reparse point",+        ));+    }+    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };+    if unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info) } == 0 {+        return Err(io::Error::last_os_error());+    }+    if info.nNumberOfLinks != 1 {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            "cache entry must not be hardlinked",+        ));+    }+    let mut owner = ptr::null_mut();+    let mut security = ptr::null_mut();+    let status = unsafe {+        GetSecurityInfo(+            file.as_raw_handle() as HANDLE,+            SE_FILE_OBJECT,+            OWNER_SECURITY_INFORMATION,+            &mut owner,+            ptr::null_mut(),+            ptr::null_mut(),+            ptr::null_mut(),+            &mut security,+        )+    };+    if status != 0 {+        return Err(io::Error::from_raw_os_error(status as i32));+    }+    let _security = LocalAllocation(security);+    let sid = current_sid()?;+    let owner = sid_string(owner)?;+    if owner != sid {+        return Err(io::Error::new(+            io::ErrorKind::PermissionDenied,+            format!(+                "cache entry {} has owner SID {owner}, expected {sid}",+                path.display()+            ),+        ));+    }+    protect_entry_dacl(path, &sid)?;+    Ok(file)+}++/// Remove inherited grants before verifying cache bytes or allowing the image+/// loader to reopen the entry by pathname.+fn protect_entry_dacl(path: &Path, sid: &str) -> io::Result<()> {+    use windows_sys::Win32::Security::{+        SetFileSecurityW, DACL_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,+    };++    let sddl: Vec<u16> = format!("O:{sid}D:P(A;;FA;;;{sid})(A;;FA;;;SY)")+        .encode_utf16()+        .chain(Some(0))+        .collect();+    let mut descriptor = ptr::null_mut();+    if unsafe {+        ConvertStringSecurityDescriptorToSecurityDescriptorW(+            sddl.as_ptr(),+            SDDL_REVISION_1,+            &mut descriptor,+            ptr::null_mut(),+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    let _descriptor = LocalAllocation(descriptor);+    if unsafe {+        SetFileSecurityW(+            wide(path).as_ptr(),+            DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,+            descriptor,+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    Ok(())+}++pub fn create_cache_root(path: &Path) -> io::Result<()> {+    fs::create_dir_all(path)+}++pub fn lock_exclusive(file: &File) -> io::Result<()> {+    use std::os::windows::io::AsRawHandle;+    let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };+    if unsafe {+        LockFileEx(+            file.as_raw_handle() as HANDLE,+            LOCKFILE_EXCLUSIVE_LOCK,+            0,+            1,+            0,+            &mut overlapped,+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    Ok(())+}++/// Try to acquire an exclusive lock without waiting for an active loader.+pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {+    use std::os::windows::io::AsRawHandle;++    let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };+    if unsafe {+        LockFileEx(+            file.as_raw_handle() as HANDLE,+            LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,+            0,+            1,+            0,+            &mut overlapped,+        )+    } != 0+    {+        return Ok(true);+    }+    let error = io::Error::last_os_error();+    if error.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32) {+        Ok(false)+    } else {+        Err(error)+    }+}++pub fn sync_directory(_path: &Path) -> io::Result<()> {+    // Windows does not expose POSIX directory fsync. All file contents are+    // flushed before the same-volume atomic rename; no partial file is visible.+    Ok(())+}++/// Replace an existing payload on the same volume. Unlike `std::fs::rename`,+/// MoveFileEx supports replacement on Windows.+pub fn replace_file(source: &Path, destination: &Path) -> io::Result<()> {+    if unsafe {+        MoveFileExW(+            wide(source).as_ptr(),+            wide(destination).as_ptr(),+            MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    Ok(())+}++/// # Safety+/// `address` must point into a currently loaded native image.+pub unsafe fn carrier_path(address: *const c_void) -> Result<PathBuf> {+    let mut module = 0;+    if GetModuleHandleExW(+        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,+        address.cast(),+        &mut module,+    ) == 0+    {+        return Err(Error::io(+            ErrorKind::Load,+            "locate native carrier module",+            io::Error::last_os_error(),+        ));+    }+    let mut buffer = vec![0; 256];+    loop {+        let len = GetModuleFileNameW(module, buffer.as_mut_ptr(), buffer.len() as u32) as usize;+        if len == 0 {+            return Err(Error::io(+                ErrorKind::Load,+                "resolve native carrier path",+                io::Error::last_os_error(),+            ));+        }+        if len < buffer.len() {+            return Ok(PathBuf::from(OsString::from_wide(&buffer[..len])));+        }+        if buffer.len() >= 32768 {+            return Err(Error::new(+                ErrorKind::Load,+                "native carrier path exceeds Windows path limit",+            ));+        }+        buffer.resize((buffer.len() * 2).min(32768), 0);+    }+}++/// Prepare a new, empty image before decoding. NTFS compression can be+/// inherited from a user-selected cache directory, even when we never request+/// it ourselves. Clear it through the existing private write handle: first+/// loading compressed DLLs is substantially slower. Published images are never+/// modified in place.+pub(crate) fn prepare_cache_image(file: &File) -> io::Result<()> {+    use std::os::windows::io::AsRawHandle;+    if file.metadata()?.file_attributes() & FILE_ATTRIBUTE_COMPRESSED == 0 {+        return Ok(());+    }+    let mut format = COMPRESSION_FORMAT_NONE;+    let mut returned = 0;+    if unsafe {+        DeviceIoControl(+            file.as_raw_handle() as HANDLE,+            FSCTL_SET_COMPRESSION,+            (&mut format as *mut u16).cast(),+            2,+            ptr::null_mut(),+            0,+            &mut returned,+            ptr::null_mut(),+        )+    } == 0+    {+        return Err(io::Error::last_os_error());+    }+    Ok(())+}++#[cfg(test)]+#[path = "windows_tests.rs"]+mod tests;

deps/swc/crates/swc_native_addon/src/replacement.rs

new file mode 100644@@ -0,0 +1,288 @@+//! Self-replacement always publishes a complete, transparently compressed file.+//! The mapped carrier is never edited, and the current process loads a+//! different inode/path: dlopen may otherwise return the already-loaded carrier+//! recursively.++use std::path::Path;++use crate::{cache::Materialized, format::Payload, Result};++/// Try the optional filesystem optimization. Errors leave the carrier intact+/// (except a directory-flush error after a completed atomic replacement) and+/// should be reported as diagnostics before falling back to the verified cache.+pub fn try_replace(payload: &Payload<'_>, carrier: &Path) -> Result<Option<Materialized>> {+    #[cfg(any(target_os = "linux", target_os = "macos"))]+    return supported::replace(payload, carrier);+    #[cfg(not(any(target_os = "linux", target_os = "macos")))]+    {+        let _ = (payload, carrier);+        // In particular, never attempt to replace a mapped Windows DLL.+        Ok(None)+    }+}++#[cfg(any(target_os = "linux", target_os = "macos"))]+mod supported {+    use std::{+        fs::{self, File},+        io::{self, Read},+        os::{fd::AsRawFd, unix::fs::MetadataExt},+        path::Path,+    };++    use tempfile::Builder;++    use crate::{+        cache,+        format::{Payload, MAX_SIZE},+        platform, Error, ErrorKind, Result,+    };++    const HEADER_SEARCH_CHUNK_SIZE: usize = 64 * 1024;++    fn fail(operation: &str, error: io::Error) -> Error {+        Error::io(ErrorKind::Cache, operation, error)+    }++    pub fn replace(payload: &Payload<'_>, carrier: &Path) -> Result<Option<cache::Materialized>> {+        let mut original =+            File::open(carrier).map_err(|e| fail("open carrier for replacement", e))?;+        if !supported_filesystem(&original)? {+            return Ok(None);+        }+        let metadata = original+            .metadata()+            .map_err(|e| fail("inspect carrier", e))?;+        if !metadata.is_file()+            || metadata.nlink() != 1+            || metadata.uid() != unsafe { libc::geteuid() }+        {+            return Ok(None);+        }+        // Lock the original inode. Competing first loads holding that inode must+        // recheck the pathname after acquiring the lock; only one can replace it.+        platform::lock_exclusive(&original).map_err(|e| fail("lock carrier", e))?;+        if !same_file(carrier, &metadata)? {+            return Ok(None);+        }+        #[cfg(target_os = "macos")]+        if platform::has_extended_acl(&original).map_err(|e| fail("inspect carrier ACL", e))? {+            // The decoded staging file would not inherit an explicit Darwin+            // ACL. Preserve installation access controls by using the cache.+            return Ok(None);+        }+        // A process may reach registration after another process has already+        // replaced the path. Require this payload's header in the source image+        // before attempting replacement, never overwrite a different raw addon.+        if metadata.len() > MAX_SIZE {+            return Ok(None);+        }+        let header = payload.header.encode();+        if !contains_header(&mut original, &header, HEADER_SEARCH_CHUNK_SIZE)+            .map_err(|e| fail("inspect carrier payload", e))?+        {+            return Ok(None);+        }+        let parent = carrier+            .parent()+            .ok_or_else(|| Error::new(ErrorKind::Cache, "carrier has no parent directory"))?;+        let mut stage = Builder::new()+            .prefix(".swc-replace-")+            .suffix(".node")+            .tempfile_in(parent)+            .map_err(|e| fail("stage carrier replacement", e))?;+        #[cfg(target_os = "macos")]+        if platform::has_extended_acl(stage.as_file())+            .map_err(|e| fail("inspect replacement staging ACL", e))?+        {+            // The parent may contribute an inheritable ACL even when the+            // carrier has none. Publishing it would silently alter the+            // carrier's access controls, so retain the carrier and use cache.+            return Ok(None);+        }+        payload.decode_into(stage.as_file_mut())?;+        platform::copy_metadata(&original, stage.as_file())+            .map_err(|e| fail("preserve carrier metadata", e))?;+        stage+            .as_file()+            .sync_all()+            .map_err(|e| fail("flush replacement", e))?;+        let candidate = compress(stage)?;+        let mut verified =+            File::open(&candidate).map_err(|e| fail("open compressed replacement", e))?;+        payload.verify_image(&mut verified)?;+        verified+            .sync_all()+            .map_err(|e| fail("flush compressed replacement", e))?;+        // Materialize the current process's distinct image before publishing.+        // Failure here leaves the original carrier available for every process.+        let temporary = cache::temporary(payload)?;+        if !same_file(carrier, &metadata)? {+            return Ok(None);+        }+        fs::rename(&candidate, carrier)+            .map_err(|e| fail("atomically replace native carrier", e))?;+        platform::sync_directory(parent).map_err(|e| fail("flush carrier directory", e))?;+        Ok(Some(temporary))+    }++    fn same_file(path: &Path, original: &fs::Metadata) -> Result<bool> {+        let current =+            fs::symlink_metadata(path).map_err(|e| fail("recheck carrier identity", e))?;+        Ok(current.is_file()+            && current.dev() == original.dev()+            && current.ino() == original.ino()+            && current.len() == original.len()+            && current.mtime() == original.mtime()+            && current.mtime_nsec() == original.mtime_nsec())+    }++    /// Search a carrier without retaining its potentially multi-gigabyte image.+    /// The overlap retains headers split between successive reads.+    fn contains_header(+        input: &mut impl Read,+        header: &[u8],+        chunk_size: usize,+    ) -> io::Result<bool> {+        debug_assert!(!header.is_empty());+        debug_assert_ne!(chunk_size, 0);++        let overlap = header.len() - 1;+        let mut bytes = vec![0; overlap + chunk_size];+        loop {+            let read = input.read(&mut bytes[overlap..])?;+            if read == 0 {+                return Ok(false);+            }+            let len = overlap + read;+            if bytes[..len]+                .windows(header.len())+                .any(|window| window == header)+            {+                return Ok(true);+            }+            if overlap != 0 {+                bytes.copy_within(len - overlap..len, 0);+            }+        }+    }++    fn supported_filesystem(file: &File) -> Result<bool> {+        let mut status: libc::statfs = unsafe { std::mem::zeroed() };+        if unsafe { libc::fstatfs(file.as_raw_fd(), &mut status) } != 0 {+            return Err(fail(+                "identify carrier filesystem",+                io::Error::last_os_error(),+            ));+        }+        #[cfg(target_os = "linux")]+        return Ok(status.f_type as u64 == 0x9123_683e); // BTRFS_SUPER_MAGIC+        #[cfg(target_os = "macos")]+        return Ok(+            unsafe { std::ffi::CStr::from_ptr(status.f_fstypename.as_ptr()) }.to_bytes() == b"apfs",+        );+    }++    #[cfg(target_os = "linux")]+    fn compress(stage: tempfile::NamedTempFile) -> Result<tempfile::TempPath> {+        // These ioctl encodings are shared by the supported 64-bit Linux ABIs.+        // Set the inode policy AND rewrite already-written extents: setting only+        // FS_COMPR_FL does not retroactively compress the decoded file.+        #[repr(C)]+        struct DefragRange {+            start: u64,+            len: u64,+            flags: u64,+            extent_thresh: u32,+            compress_type: u32,+            unused: [u32; 4],+        }+        let fd = stage.as_file().as_raw_fd();+        let mut flags: libc::c_long = 0;+        if unsafe { libc::ioctl(fd, 0x8008_6601_u32 as _, &mut flags) } != 0 {+            return Err(fail("read btrfs inode flags", io::Error::last_os_error()));+        }+        flags = (flags | 0x4) & !0x400; // FS_COMPR_FL, clear FS_NOCOMP_FL+        if unsafe { libc::ioctl(fd, 0x4008_6602, &flags) } != 0 {+            return Err(fail("enable btrfs compression", io::Error::last_os_error()));+        }+        let range = DefragRange {+            start: 0,+            len: u64::MAX,+            flags: 3,+            extent_thresh: 1,+            compress_type: 1,+            unused: [0; 4], // zlib works on all supported btrfs kernels+        };+        if unsafe { libc::ioctl(fd, 0x4030_9410, &range) } != 0 {+            return Err(fail("compress btrfs extents", io::Error::last_os_error()));+        }+        stage+            .as_file()+            .sync_all()+            .map_err(|e| fail("flush btrfs extents", e))?;+        Ok(stage.into_temp_path())+    }++    #[cfg(target_os = "macos")]+    fn compress(stage: tempfile::NamedTempFile) -> Result<tempfile::TempPath> {+        use std::{os::macos::fs::MetadataExt as MacMetadataExt, process::Command};+        let parent = stage.path().parent().unwrap();+        let candidate = Builder::new()+            .prefix(".swc-compressed-")+            .suffix(".node")+            .tempfile_in(parent)+            .map_err(|e| fail("stage APFS compression", e))?+            .into_temp_path();+        // Apple's installed copier maintains the decmpfs resource-fork format.+        // Only a private staging file is passed to it, never the mapped carrier.+        let output = Command::new("/usr/bin/ditto")+            .args(["--hfsCompression", "--rsrc", "--extattr", "--acl", "--qtn"])+            .arg(stage.path())+            .arg(&candidate)+            .env_remove("DITTOABORT")+            .env_remove("DITTONORSRC")+            .env_remove("DITTOKEEPBINARIESPATTERN")+            .env_remove("DITTOKEEPBINARIESDIR")+            .env_remove("DITTO_TEST_OPTIONS")+            .output()+            .map_err(|e| fail("run APFS compression", e))?;+        if !output.status.success() {+            return Err(Error::new(+                ErrorKind::Cache,+                format!(+                    "APFS compression failed: {}",+                    String::from_utf8_lossy(&output.stderr)+                ),+            ));+        }+        let metadata = fs::metadata(&candidate).map_err(|e| fail("inspect APFS compression", e))?;+        if MacMetadataExt::st_flags(&metadata) & 0x20 == 0 {+            // UF_COMPRESSED+            return Err(Error::new(+                ErrorKind::Cache,+                "APFS declined transparent compression",+            ));+        }+        Ok(candidate)+    }++    #[cfg(test)]+    mod tests {+        use std::io::Cursor;++        use super::contains_header;++        #[test]+        fn finds_embedded_header_across_read_boundaries() {+            let mut input = Cursor::new(b"prefix-embedded-header-suffix");+            assert!(contains_header(&mut input, b"embedded-header", 8).unwrap());+        }++        #[test]+        fn rejects_carrier_without_embedded_header() {+            let mut input = Cursor::new(b"unrelated carrier bytes");+            assert!(!contains_header(&mut input, b"embedded-header", 8).unwrap());+        }+    }+}

deps/swc/crates/swc_node_bundler/Cargo.toml

@@ -7,7 +7,7 @@ edition       = { workspace = true } license       = { workspace = true } name          = "swc_node_bundler" repository    = { workspace = true }-version       = "77.0.0"+version       = "81.0.0"  [lib] bench = false@@ -25,27 +25,27 @@ serde      = { workspace = true, features = ["derive"], optional = true } tracing    = { workspace = true }  string_enum = { version = "1.0.2", path = "../string_enum" }-swc = { version = "76.0.0", path = "../swc" }+swc = { version = "80.0.0", path = "../swc" } swc_atoms = { version = "10.0.0", path = "../swc_atoms" }-swc_bundler = { version = "56.0.1", path = "../swc_bundler", features = [+swc_bundler = { version = "57.0.0", path = "../swc_bundler", features = [   "concurrent", ] } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "concurrent", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" } swc_ecma_loader = { version = "27.0.0", path = "../swc_ecma_loader" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript", ] }-swc_ecma_transforms = { version = "62.0.0", path = "../swc_ecma_transforms" }-swc_ecma_utils = { version = "35.0.1", path = "../swc_ecma_utils" }+swc_ecma_transforms = { version = "64.0.0", path = "../swc_ecma_transforms" }+swc_ecma_utils = { version = "35.0.3", path = "../swc_ecma_utils" } swc_malloc = { version = "1.2.5", path = "../swc_malloc" }  [dev-dependencies] -swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base", features = [+swc_ecma_transforms_base = { version = "50.0.0", path = "../swc_ecma_transforms_base", features = [   "inline-helpers", ] } testing = { version = "27.0.0", path = "../testing" }

deps/swc/crates/swc_plugin_backend_tests/Cargo.toml

@@ -34,10 +34,10 @@ swc_common = { version = "26.0.0", path = "../swc_common", features = [   "concurrent", ] } swc_plugin_runner = { version = "34.0.0", path = "../swc_plugin_runner", features = ["encoding-impl", "plugin_transform_schema_v1"] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", features = [   "encoding-impl", ] }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser" } swc_ecma_visit = { version = "29.0.0", path = "../swc_ecma_visit" } swc_transform_common = { version = "20.0.0", path = "../swc_transform_common" } swc_malloc = { version = "1.2.5", path = "../swc_malloc" }

deps/swc/crates/swc_plugin_proxy/Cargo.toml

@@ -26,4 +26,4 @@ cbor4ii    = { workspace = true, features = [ "use_std" ], optional = true }  better_scoped_tls = { version = "1.0.1", path = "../better_scoped_tls" } swc_common        = { version = "26.0.0", path = "../swc_common" }-swc_ecma_ast      = { version = "29.0.0", path = "../swc_ecma_ast" }+swc_ecma_ast      = { version = "29.0.2", path = "../swc_ecma_ast" }

deps/swc/crates/swc_plugin_runner/Cargo.toml

@@ -38,7 +38,7 @@ swc_atoms = { version = "10.0.0", path = '../swc_atoms' } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "concurrent", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast", optional = true }+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast", optional = true } swc_plugin_proxy = { version = "30.0.0", path = "../swc_plugin_proxy" } swc_transform_common = { version = "20.0.0", path = "../swc_transform_common" } 

deps/swc/crates/swc_ts_fast_strip/Cargo.toml

@@ -7,7 +7,7 @@ include       = ["Cargo.toml", "src/**/*.rs"] license       = { workspace = true } name          = "swc_ts_fast_strip" repository    = { workspace = true }-version       = "58.0.0"+version       = "59.0.0"  [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }@@ -24,15 +24,15 @@ swc_atoms = { version = "10.0.0", path = "../swc_atoms" } swc_common = { version = "26.0.0", path = "../swc_common", features = [   "sourcemap", ] }-swc_ecma_ast = { version = "29.0.0", path = "../swc_ecma_ast" }-swc_ecma_codegen = { version = "32.0.0", path = "../swc_ecma_codegen" }-swc_ecma_parser = { version = "45.1.1", path = "../swc_ecma_parser", default-features = false, features = [+swc_ecma_ast = { version = "29.0.2", path = "../swc_ecma_ast" }+swc_ecma_codegen = { version = "32.0.3", path = "../swc_ecma_codegen" }+swc_ecma_parser = { version = "46.0.0", path = "../swc_ecma_parser", default-features = false, features = [   "typescript",   "unstable", ] }-swc_ecma_transforms_base = { version = "49.0.1", path = "../swc_ecma_transforms_base" }

The diff stops at 1024 KiB. git show 4caa87cd shows the rest.