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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, ¶m.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
@@ -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
@@ -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
@@ -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
@@ -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+*.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
@@ -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 ¶m.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) = ¶m.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 ¶m.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(¶m.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(¶m.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(¶m.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 ¶ms {- 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 ¶ms {+ 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
@@ -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(¶ms); 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(¶ms); 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(¶ms); 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(¶ms); 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(¶m.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) = ¶m.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 ¶ms {- 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 ¶ms {+ 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(¶ms);- 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(¶ms);+ 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.