diff --git a/deps/swc/Cargo.lock b/deps/swc/Cargo.lock index 7f19fba81..f88fa1768 100644 --- a/deps/swc/Cargo.lock +++ b/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", ] diff --git a/deps/swc/Cargo.toml b/deps/swc/Cargo.toml index ddaea69db..b8d5a5041 100644 --- a/deps/swc/Cargo.toml +++ b/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" diff --git a/deps/swc/bindings/binding_core_node/src/parse.rs b/deps/swc/bindings/binding_core_node/src/parse.rs index 07156e260..144295b25 100644 --- a/deps/swc/bindings/binding_core_node/src/parse.rs +++ b/deps/swc/bindings/binding_core_node/src/parse.rs @@ -234,7 +234,7 @@ pub fn parse_file_sync(path: String, opts: Buffer) -> napi::Result { 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 { diff --git a/deps/swc/bindings/binding_core_node/src/transform.rs b/deps/swc/bindings/binding_core_node/src/transform.rs index c0ac1367a..015c338fe 100644 --- a/deps/swc/bindings/binding_core_node/src/transform.rs +++ b/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) }) }, diff --git a/deps/swc/bindings/binding_core_wasm/Cargo.toml b/deps/swc/bindings/binding_core_wasm/Cargo.toml index db6fc0f03..123982b37 100644 --- a/deps/swc/bindings/binding_core_wasm/Cargo.toml +++ b/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 diff --git a/deps/swc/bindings/binding_core_wasm/example/package.json b/deps/swc/bindings/binding_core_wasm/example/package.json index 879cbee47..5bc2c1670 100644 --- a/deps/swc/bindings/binding_core_wasm/example/package.json +++ b/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" diff --git a/deps/swc/bindings/binding_core_wasm/package.json b/deps/swc/bindings/binding_core_wasm/package.json index 7d95ecb67..b4599bb9f 100644 --- a/deps/swc/bindings/binding_core_wasm/package.json +++ b/deps/swc/bindings/binding_core_wasm/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "@rstest/core": "0.7.8" + "@rstest/core": "0.7.9" } } diff --git a/deps/swc/bindings/binding_core_wasm/src/types.rs b/deps/swc/bindings/binding_core_wasm/src/types.rs index 0fa076e72..4597f99a9 100644 --- a/deps/swc/bindings/binding_core_wasm/src/types.rs +++ b/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"; diff --git a/deps/swc/bindings/binding_es_ast_viewer/Cargo.toml b/deps/swc/bindings/binding_es_ast_viewer/Cargo.toml index f7f5d2753..bedbabd6c 100644 --- a/deps/swc/bindings/binding_es_ast_viewer/Cargo.toml +++ b/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 } diff --git a/deps/swc/bindings/binding_html_wasm/Cargo.toml b/deps/swc/bindings/binding_html_wasm/Cargo.toml index 746293e3d..8b79b773e 100644 --- a/deps/swc/bindings/binding_html_wasm/Cargo.toml +++ b/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 diff --git a/deps/swc/bindings/binding_minifier_wasm/Cargo.toml b/deps/swc/bindings/binding_minifier_wasm/Cargo.toml index 9ca6e3380..5e601cdda 100644 --- a/deps/swc/bindings/binding_minifier_wasm/Cargo.toml +++ b/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 diff --git a/deps/swc/bindings/binding_minifier_wasm/src/types.rs b/deps/swc/bindings/binding_minifier_wasm/src/types.rs index 577a7d116..d2371edef 100644 --- a/deps/swc/bindings/binding_minifier_wasm/src/types.rs +++ b/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"; diff --git a/deps/swc/bindings/binding_native_addon/Cargo.toml b/deps/swc/bindings/binding_native_addon/Cargo.toml new file mode 100644 index 000000000..265a61f83 --- /dev/null +++ b/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", +] } diff --git a/deps/swc/bindings/binding_native_addon/NOTICE b/deps/swc/bindings/binding_native_addon/NOTICE new file mode 100644 index 000000000..7f7f8b493 --- /dev/null +++ b/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. diff --git a/deps/swc/bindings/binding_native_addon/build.rs b/deps/swc/bindings/binding_native_addon/build.rs new file mode 100644 index 000000000..5589981a8 --- /dev/null +++ b/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(); +} diff --git a/deps/swc/bindings/binding_native_addon/src/legacy.rs b/deps/swc/bindings/binding_native_addon/src/legacy.rs new file mode 100644 index 000000000..7f96e8f83 --- /dev/null +++ b/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::(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; diff --git a/deps/swc/bindings/binding_native_addon/src/lib.rs b/deps/swc/bindings/binding_native_addon/src/lib.rs new file mode 100644 index 000000000..168d7fbef --- /dev/null +++ b/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> = Mutex::new(None); +static IMAGE_ANCHOR: u8 = 0; + +fn replacement_carrier( + mode: &CacheMode, + resolve: impl FnOnce() -> Result, +) -> Option { + // 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 { + 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::(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()); + } + } +} diff --git a/deps/swc/bindings/binding_native_addon/src/napi.rs b/deps/swc/bindings/binding_native_addon/src/napi.rs new file mode 100644 index 000000000..40f750cb2 --- /dev/null +++ b/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 { + #[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::(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::(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() + } +} diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/Cargo.toml b/deps/swc/bindings/binding_nodejs_support_wasm/Cargo.toml index 8902ebafb..fd9f983cc 100644 --- a/deps/swc/bindings/binding_nodejs_support_wasm/Cargo.toml +++ b/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 diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/README.md b/deps/swc/bindings/binding_nodejs_support_wasm/README.md index abd29b9bb..a6d0d741f 100644 --- a/deps/swc/bindings/binding_nodejs_support_wasm/README.md +++ b/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 diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/lib.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/lib.rs index e971058ac..de3de2782 100644 --- a/deps/swc/bindings/binding_nodejs_support_wasm/src/lib.rs +++ b/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 { 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 { + 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 { + let input = coerce_input(input)?; + Ok(serde_wasm_bindgen::to_value( + &nodejs::find_top_level_awaits(&input), + )?) +} + fn coerce_input(input: JsValue) -> Result { match input.as_string() { Some(input) => Ok(input), diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs.rs index 50bcd7c1e..8f2c7e5c7 100644 --- a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs.rs +++ b/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, hoisted: Vec, - import_bindings: Vec, - 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 { + fn import_decl_to_script(&self, node: &ImportDecl) -> Option { 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, 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, - replaced_ranges: &'b [(usize, usize)], - bindings: &'b [ImportBinding], - scopes: Vec>, -} - -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, 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 { - 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 { - 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, - 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, -} - -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 { - 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, 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, - 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, 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, - ident: &BindingIdent, - bindings: &[ImportBinding], -) { - push_shadowed_ident_binding(out, ident.id.sym.as_ref(), bindings); -} - -fn push_shadowed_ident_binding(out: &mut Vec, 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 { +fn lex_tokens(code: &str) -> Vec { 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 == "", + SyntaxError::Expected(_, got) | SyntaxError::Unexpected { got, .. } => got == "", _ => 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"]) ) ); diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/awaits.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/awaits.rs new file mode 100644 index 000000000..50cbe98b1 --- /dev/null +++ b/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 { + 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, +} + +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) {} +} diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/fixtures.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/fixtures.rs new file mode 100644 index 000000000..ed4eece9b --- /dev/null +++ b/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 = 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 == "")), "errors: {errors:?}"); + Value::Bool(true) + }); +} diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/position.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/position.rs new file mode 100644 index 000000000..66fc0e522 --- /dev/null +++ b/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; + } +} diff --git a/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/tokenize.rs b/deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs/tokenize.rs new file mode 100644 index 000000000..106c08dcb --- /dev/null +++ b/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 { + 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, kind: TokenKind, start: usize, end: usize) { + if start < end { + tokens.push(Token { kind, start, end }); + } +} diff --git a/deps/swc/bindings/binding_typescript_wasm/Cargo.toml b/deps/swc/bindings/binding_typescript_wasm/Cargo.toml index b62fd90d7..bbc1cf9c5 100644 --- a/deps/swc/bindings/binding_typescript_wasm/Cargo.toml +++ b/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 diff --git a/deps/swc/bindings/swc_cli/Cargo.toml b/deps/swc/bindings/swc_cli/Cargo.toml index 759b029e1..f92a5542a 100644 --- a/deps/swc/bindings/swc_cli/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/binding_macros/Cargo.toml b/deps/swc/crates/binding_macros/Cargo.toml index 3e24ca62d..66a7e5140 100644 --- a/deps/swc/crates/binding_macros/Cargo.toml +++ b/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 diff --git a/deps/swc/crates/dbg-swc/Cargo.toml b/deps/swc/crates/dbg-swc/Cargo.toml index b31347e95..ace680d3f 100644 --- a/deps/swc/crates/dbg-swc/Cargo.toml +++ b/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 } diff --git a/deps/swc/crates/hstr/Cargo.toml b/deps/swc/crates/hstr/Cargo.toml index 64c9afcca..73ca7cd08 100644 --- a/deps/swc/crates/hstr/Cargo.toml +++ b/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 diff --git a/deps/swc/crates/hstr/src/wtf8/mod.rs b/deps/swc/crates/hstr/src/wtf8/mod.rs index 12bca81a5..765f6fb0c 100644 --- a/deps/swc/crates/hstr/src/wtf8/mod.rs +++ b/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""); diff --git a/deps/swc/crates/jsdoc/Cargo.toml b/deps/swc/crates/jsdoc/Cargo.toml index 947f6fd67..e6c50d06b 100644 --- a/deps/swc/crates/jsdoc/Cargo.toml +++ 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" } diff --git a/deps/swc/crates/preset_env_base/Cargo.toml b/deps/swc/crates/preset_env_base/Cargo.toml index eeb0dbe1c..10321aeac 100644 --- a/deps/swc/crates/preset_env_base/Cargo.toml +++ b/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 diff --git a/deps/swc/crates/swc-ast-explorer/Cargo.toml b/deps/swc/crates/swc-ast-explorer/Cargo.toml index 2111fd5cf..a86dfa382 100644 --- a/deps/swc/crates/swc-ast-explorer/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc/Cargo.toml b/deps/swc/crates/swc/Cargo.toml index 2a24db42c..e34790d40 100644 --- a/deps/swc/crates/swc/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc/src/config/mod.rs b/deps/swc/crates/swc/src/config/mod.rs index 1ab9fb400..a546958c3 100644 --- a/deps/swc/crates/swc/src/config/mod.rs +++ b/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)> { 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)> { None } diff --git a/deps/swc/crates/swc/src/lib.rs b/deps/swc/crates/swc/src/lib.rs index 90f1c3ff6..6810e6f89 100644 --- a/deps/swc/crates/swc/src/lib.rs +++ b/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); diff --git a/deps/swc/crates/swc_atoms/Cargo.toml b/deps/swc/crates/swc_atoms/Cargo.toml index 1bac4407b..1e5132c4d 100644 --- a/deps/swc/crates/swc_atoms/Cargo.toml +++ b/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"] } diff --git a/deps/swc/crates/swc_bundler/Cargo.toml b/deps/swc/crates/swc_bundler/Cargo.toml index 91e864741..9c8fbc904 100644 --- a/deps/swc/crates/swc_bundler/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_cli_impl/Cargo.toml b/deps/swc/crates/swc_cli_impl/Cargo.toml index 351052481..cd1ac87a1 100644 --- a/deps/swc/crates/swc_cli_impl/Cargo.toml +++ b/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 } diff --git a/deps/swc/crates/swc_cli_impl/src/commands/compile.rs b/deps/swc/crates/swc_cli_impl/src/commands/compile.rs index a6fe2ec31..2ac4ce5d1 100644 --- a/deps/swc/crates/swc_cli_impl/src/commands/compile.rs +++ b/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, file_path: &Path, + output_file_path: &Path, ) -> anyhow::Result { - 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) } diff --git a/deps/swc/crates/swc_cli_impl/src/commands/compile/paths.rs b/deps/swc/crates/swc_cli_impl/src/commands/compile/paths.rs index 3302e5d81..6b6d3648e 100644 --- a/deps/swc/crates/swc_cli_impl/src/commands/compile/paths.rs +++ b/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 { + 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, diff --git a/deps/swc/crates/swc_compiler_base/Cargo.toml b/deps/swc/crates/swc_compiler_base/Cargo.toml index be07a9e4a..93ed012b5 100644 --- a/deps/swc/crates/swc_compiler_base/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_config/Cargo.toml b/deps/swc/crates/swc_config/Cargo.toml index ad4d24161..2fe7e89cf 100644 --- a/deps/swc/crates/swc_config/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc_config/src/types/bool_or_data.rs b/deps/swc/crates/swc_config/src/types/bool_or_data.rs index 5043fdea0..5c2b1d51d 100644 --- a/deps/swc/crates/swc_config/src/types/bool_or_data.rs +++ b/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) diff --git a/deps/swc/crates/swc_core/Cargo.toml b/deps/swc/crates/swc_core/Cargo.toml index 56cac5c90..2d56fcfc3 100644 --- a/deps/swc/crates/swc_core/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc_css/Cargo.toml b/deps/swc/crates/swc_css/Cargo.toml index 006b63575..368d261a7 100644 --- a/deps/swc/crates/swc_css/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_css_lints/Cargo.toml b/deps/swc/crates/swc_css_lints/Cargo.toml index 63f3e2e60..8fc6eb8b7 100644 --- a/deps/swc/crates/swc_css_lints/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_css_prefixer/Cargo.toml b/deps/swc/crates/swc_css_prefixer/Cargo.toml index 77a5cd55d..556681ba2 100644 --- a/deps/swc/crates/swc_css_prefixer/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_ast/Cargo.toml b/deps/swc/crates/swc_ecma_ast/Cargo.toml index 1a287e980..ee66b9280 100644 --- a/deps/swc/crates/swc_ecma_ast/Cargo.toml +++ b/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 diff --git a/deps/swc/crates/swc_ecma_ast/src/expr.rs b/deps/swc/crates/swc_ecma_ast/src/expr.rs index eae495eb6..b83a5bda7 100644 --- a/deps/swc/crates/swc_ecma_ast/src/expr.rs +++ b/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 }, diff --git a/deps/swc/crates/swc_ecma_ast/src/lit.rs b/deps/swc/crates/swc_ecma_ast/src/lit.rs index 307198057..829473ec5 100644 --- a/deps/swc/crates/swc_ecma_ast/src/lit.rs +++ b/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); } diff --git a/deps/swc/crates/swc_ecma_codegen/.gitignore b/deps/swc/crates/swc_ecma_codegen/.gitignore index d23e9a511..f7b7736ae 100644 --- a/deps/swc/crates/swc_ecma_codegen/.gitignore +++ b/deps/swc/crates/swc_ecma_codegen/.gitignore @@ -1,2 +1,5 @@ /output.js -*.map \ No newline at end of file +*.map + +# Keep expected source-map snapshots for the template escape fixtures. +!tests/fixture/template-unicode-escapes/**/output*.map diff --git a/deps/swc/crates/swc_ecma_codegen/Cargo.toml b/deps/swc/crates/swc_ecma_codegen/Cargo.toml index 3b0f30713..45f23ebb6 100644 --- a/deps/swc/crates/swc_ecma_codegen/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_codegen/src/lib.rs b/deps/swc/crates/swc_ecma_codegen/src/lib.rs index 2dee0e203..14c731ec0 100644 --- a/deps/swc/crates/swc_ecma_codegen/src/lib.rs +++ b/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 = 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 = + Lazy::new(|| regex::Regex::new(r"(\\(?:n|x0[aA]|u000[aA]|u\{0*[aA]\}|\n)|\n)|\\.").unwrap()); impl 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, - buf: &mut String, - iter: impl Iterator, - ) { - 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 { diff --git a/deps/swc/crates/swc_ecma_codegen/src/lit.rs b/deps/swc/crates/swc_ecma_codegen/src/lit.rs index a932f44d1..4fb87d5a9 100644 --- a/deps/swc/crates/swc_ecma_codegen/src/lit.rs +++ b/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("") { + CowStr::Owned( + value + .replace("", "--\\x3e") + .into(), + ) + } else { + value + } +} + +fn replace_close_inline_script(raw: &str) -> CowStr<'_> { let chars = raw.as_bytes(); let pattern_len = 8; // @@ -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) diff --git a/deps/swc/crates/swc_ecma_codegen/src/template.rs b/deps/swc/crates/swc_ecma_codegen/src/template.rs new file mode 100644 index 000000000..4a47c55b9 --- /dev/null +++ b/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>) -> Option { + 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>, + ascii_only: bool, +) -> Option { + 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>) -> Option { + 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) +} diff --git a/deps/swc/crates/swc_ecma_codegen/src/typescript.rs b/deps/swc/crates/swc_ecma_codegen/src/typescript.rs index ef57f9499..0e03b7728 100644 --- a/deps/swc/crates/swc_ecma_codegen/src/typescript.rs +++ b/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)?; diff --git a/deps/swc/crates/swc_ecma_compat_bugfixes/Cargo.toml b/deps/swc/crates/swc_ecma_compat_bugfixes/Cargo.toml index c7ae46e65..5d5096a74 100644 --- a/deps/swc/crates/swc_ecma_compat_bugfixes/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_common/Cargo.toml b/deps/swc/crates/swc_ecma_compat_common/Cargo.toml index 7431a9039..5b31fdaba 100644 --- a/deps/swc/crates/swc_ecma_compat_common/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es2015/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2015/Cargo.toml index 320ff23a7..4cc6ca0cb 100644 --- a/deps/swc/crates/swc_ecma_compat_es2015/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs b/deps/swc/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs index d4171b337..edb91095e 100644 --- a/deps/swc/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs +++ b/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); diff --git a/deps/swc/crates/swc_ecma_compat_es2015/src/for_of.rs b/deps/swc/crates/swc_ecma_compat_es2015/src/for_of.rs index b0cd36b5b..38952cf81 100644 --- a/deps/swc/crates/swc_ecma_compat_es2015/src/for_of.rs +++ b/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, } +/// 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, + 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), } diff --git a/deps/swc/crates/swc_ecma_compat_es2015/src/template_literal.rs b/deps/swc/crates/swc_ecma_compat_es2015/src/template_literal.rs index eb37413c2..bb1d888f2 100644 --- a/deps/swc/crates/swc_ecma_compat_es2015/src/template_literal.rs +++ b/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, }) diff --git a/deps/swc/crates/swc_ecma_compat_es2016/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2016/Cargo.toml index d7aaeca79..114824563 100644 --- a/deps/swc/crates/swc_ecma_compat_es2016/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es2017/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2017/Cargo.toml index def93feab..7b8d18d2c 100644 --- a/deps/swc/crates/swc_ecma_compat_es2017/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc_ecma_compat_es2018/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2018/Cargo.toml index 8b51190df..a655ddceb 100644 --- a/deps/swc/crates/swc_ecma_compat_es2018/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc_ecma_compat_es2019/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2019/Cargo.toml index 4d73350f7..05e646bf6 100644 --- a/deps/swc/crates/swc_ecma_compat_es2019/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es2020/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2020/Cargo.toml index 62c4dce03..340a35d8e 100644 --- a/deps/swc/crates/swc_ecma_compat_es2020/Cargo.toml +++ b/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] diff --git a/deps/swc/crates/swc_ecma_compat_es2021/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2021/Cargo.toml index 02eb45055..5f6b5353e 100644 --- a/deps/swc/crates/swc_ecma_compat_es2021/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es2022/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es2022/Cargo.toml index f28fd6357..458147faf 100644 --- a/deps/swc/crates/swc_ecma_compat_es2022/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_compat_es3/Cargo.toml b/deps/swc/crates/swc_ecma_compat_es3/Cargo.toml index 3def7c16d..6085f2355 100644 --- a/deps/swc/crates/swc_ecma_compat_es3/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_ext_transforms/Cargo.toml b/deps/swc/crates/swc_ecma_ext_transforms/Cargo.toml index 50ea7873a..dd9b685a7 100644 --- a/deps/swc/crates/swc_ecma_ext_transforms/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_hooks/Cargo.toml b/deps/swc/crates/swc_ecma_hooks/Cargo.toml index 3bb2e2838..a17d5ede4 100644 --- a/deps/swc/crates/swc_ecma_hooks/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_lexer/Cargo.toml b/deps/swc/crates/swc_ecma_lexer/Cargo.toml index 2f5bcfbdb..c207fbb4f 100644 --- a/deps/swc/crates/swc_ecma_lexer/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_lexer/src/common/parser/module_item.rs b/deps/swc/crates/swc_ecma_lexer/src/common/parser/module_item.rs index ad8e68f7e..73105ae21 100644 --- a/deps/swc/crates/swc_ecma_lexer/src/common/parser/module_item.rs +++ b/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'` diff --git a/deps/swc/crates/swc_ecma_lexer/src/common/parser/typescript.rs b/deps/swc/crates/swc_ecma_lexer/src/common/parser/typescript.rs index c5e92b7cb..cb8c392b7 100644 --- a/deps/swc/crates/swc_ecma_lexer/src/common/parser/typescript.rs +++ b/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>(p: &mut P) -> PResult /* 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> || 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) diff --git a/deps/swc/crates/swc_ecma_lexer/src/lexer/state.rs b/deps/swc/crates/swc_ecma_lexer/src/lexer/state.rs index 0fd471cf2..694112bab 100644 --- a/deps/swc/crates/swc_ecma_lexer/src/lexer/state.rs +++ b/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); diff --git a/deps/swc/crates/swc_ecma_lexer/src/parser/typescript.rs b/deps/swc/crates/swc_ecma_lexer/src/parser/typescript.rs index 91b79a304..4fdab0c65 100644 --- a/deps/swc/crates/swc_ecma_lexer/src/parser/typescript.rs +++ b/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()); + } } diff --git a/deps/swc/crates/swc_ecma_lints/Cargo.toml b/deps/swc/crates/swc_ecma_lints/Cargo.toml index 72f0ff076..3993571e1 100644 --- a/deps/swc/crates/swc_ecma_lints/Cargo.toml +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_minifier/AGENTS.md b/deps/swc/crates/swc_ecma_minifier/AGENTS.md index a49776bd7..82fbfb277 100644 --- a/deps/swc/crates/swc_ecma_minifier/AGENTS.md +++ b/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. diff --git a/deps/swc/crates/swc_ecma_minifier/Cargo.toml b/deps/swc/crates/swc_ecma_minifier/Cargo.toml index fcc4791c4..f6bfb9abe 100644 --- a/deps/swc/crates/swc_ecma_minifier/Cargo.toml +++ b/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 } diff --git a/deps/swc/crates/swc_ecma_minifier/fuzz/Cargo.lock b/deps/swc/crates/swc_ecma_minifier/fuzz/Cargo.lock index 49fb4d9b0..e41c3ecba 100644 --- a/deps/swc/crates/swc_ecma_minifier/fuzz/Cargo.lock +++ b/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", diff --git a/deps/swc/crates/swc_ecma_minifier/package.json b/deps/swc/crates/swc_ecma_minifier/package.json index cfa2bd5f6..19cb557e5 100644 --- a/deps/swc/crates/swc_ecma_minifier/package.json +++ b/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" } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/hoist_decls.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/hoist_decls.rs index 1bc7b0481..54cea04cd 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/hoist_decls.rs +++ b/deps/swc/crates/swc_ecma_minifier/src/compress/hoist_decls.rs @@ -49,7 +49,6 @@ impl Hoister<'_> { Vec: for<'aa> VisitMutWith> + VisitWith>, { 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(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)); + } +} diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs index 06c9d370f..6d31f2450 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs +++ b/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, + /// Reassigned original parameters, indexed by their `arguments` slot. + reassigned_params: Vec, 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) { diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs index 4f6c6d4c1..5e04e789a 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs +++ b/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 { diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs index 39eb1e8f1..4de9d6507 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs +++ b/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) { diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index 11b56b6d8..e8397e4a9 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/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 { + 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 diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 6626a4285..0f873be15 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/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 { diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index f4a1c282f..5a5f57508 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/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); diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index 0935b3a59..98dbd0eda 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/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; diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/ops.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/ops.rs index b452e8a83..1d6ab6b62 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/ops.rs +++ b/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, } } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/props.rs index 599e8415e..5e36d9420 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/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 diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs index 62f669cdc..e26c2f648 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/rest_params.rs +++ b/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(); diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index c452381e5..c92657842 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/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 { - 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); } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/strings.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/strings.rs index 01d796f09..8b87dbf0d 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/strings.rs +++ b/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())); } } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unsafes.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unsafes.rs index c0ad97aec..f5d5621f6 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unsafes.rs +++ b/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 + }); } } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 70f21602f..70a626c70 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/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, diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/util.rs index be82d66b4..781492ab7 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/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, + 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, +} + +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 { append(pat, &mut idents); idents } + +pub(crate) fn may_inline_arrow(a: &ArrowExpr) -> bool { + !(contains_super(&a.body) || contains_this_expr(&a.body)) +} diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/arrows.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/arrows.rs index 9922f3be5..36c159910 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/arrows.rs +++ b/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; } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/bools.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/bools.rs index b56597288..17343bce5 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/bools.rs +++ b/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, diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs index 5e11c278a..fd2731f4a 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs +++ b/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); } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index 602fa4a3a..a97c8668d 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/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 { 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 { 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::().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) -> Option { + 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) } diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/if_return.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/if_return.rs index 8a1c74ec1..4f670c8ed 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/if_return.rs +++ b/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; diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/loops.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/loops.rs index 9e5750711..df1688a8d 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/loops.rs +++ b/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(..) diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/misc.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/misc.rs index d8af37512..798efe4a1 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/misc.rs +++ b/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> { let mut exprs = Vec::new(); @@ -123,7 +102,7 @@ fn collect_exprs_from_object(obj: &mut ObjectLit) -> Vec> { #[derive(Debug)] enum GroupType<'a> { - Literals(Vec<&'a ExprOrSpread>), + Literals(Vec>), 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, span: &mut Span) -> Option { + // 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) + } +} diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/mod.rs index b0614a125..ff9e506db 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/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) { diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/numbers.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/numbers.rs index cd8d8f141..3e8d468db 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/numbers.rs +++ b/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'"); diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/sequences.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/sequences.rs index dfcee072b..c51f99405 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/sequences.rs +++ 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, diff --git a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/switches.rs b/deps/swc/crates/swc_ecma_minifier/src/compress/pure/switches.rs index 9683c3c7e..616ddb073 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/compress/pure/switches.rs +++ b/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) -> 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) {} diff --git a/deps/swc/crates/swc_ecma_minifier/src/eval.rs b/deps/swc/crates/swc_ecma_minifier/src/eval.rs index facceeb41..1003c8e7b 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/eval.rs +++ b/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 { + self.eval_tpl_with_quasis(q, q.quasis.clone()) + } + + fn eval_tpl_raw(&mut self, q: &Tpl) -> Option { + 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) -> Option { self.run(); let mut exprs = Vec::new(); @@ -280,7 +299,7 @@ impl Evaluator { let mut e: Box = 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 { match lit { EvalResult::Lit(v) => match v { diff --git a/deps/swc/crates/swc_ecma_minifier/src/pass/global_defs.rs b/deps/swc/crates/swc_ecma_minifier/src/pass/global_defs.rs index 5b8db70f4..9051cd8dc 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/pass/global_defs.rs +++ b/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), } } } diff --git a/deps/swc/crates/swc_ecma_minifier/src/pass/postcompress.rs b/deps/swc/crates/swc_ecma_minifier/src/pass/postcompress.rs index 067ebd42c..1bda815dd 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/pass/postcompress.rs +++ b/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, - /// 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, diff --git a/deps/swc/crates/swc_ecma_minifier/src/usage_analyzer/analyzer/mod.rs b/deps/swc/crates/swc_ecma_minifier/src/usage_analyzer/analyzer/mod.rs index 678a4f5d3..0ed40ece1 100644 --- a/deps/swc/crates/swc_ecma_minifier/src/usage_analyzer/analyzer/mod.rs +++ b/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) { @@ -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>(&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, +} + +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)) { diff --git a/deps/swc/crates/swc_ecma_parser/Cargo.toml b/deps/swc/crates/swc_ecma_parser/Cargo.toml index b43b07463..7185de273 100644 --- a/deps/swc/crates/swc_ecma_parser/Cargo.toml +++ b/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", ] } diff --git a/deps/swc/crates/swc_ecma_parser/src/context.rs b/deps/swc/crates/swc_ecma_parser/src/context.rs index 7e8b57d26..f06949ab8 100644 --- a/deps/swc/crates/swc_ecma_parser/src/context.rs +++ b/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" diff --git a/deps/swc/crates/swc_ecma_parser/src/error.rs b/deps/swc/crates/swc_ecma_parser/src/error.rs index d6ab363a0..a8c0fd8b8 100644 --- a/deps/swc/crates/swc_ecma_parser/src/error.rs +++ b/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() diff --git a/deps/swc/crates/swc_ecma_parser/src/lexer/capturing.rs b/deps/swc/crates/swc_ecma_parser/src/lexer/capturing.rs index 3c1d3b47b..e9bd23ba3 100644 --- a/deps/swc/crates/swc_ecma_parser/src/lexer/capturing.rs +++ b/deps/swc/crates/swc_ecma_parser/src/lexer/capturing.rs @@ -89,6 +89,12 @@ impl Tokens for Capturing { 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); } diff --git a/deps/swc/crates/swc_ecma_parser/src/lexer/comments_buffer.rs b/deps/swc/crates/swc_ecma_parser/src/lexer/comments_buffer.rs index 5ec0ce7ae..06b20db4f 100644 --- a/deps/swc/crates/swc_ecma_parser/src/lexer/comments_buffer.rs +++ b/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); diff --git a/deps/swc/crates/swc_ecma_parser/src/lexer/mod.rs b/deps/swc/crates/swc_ecma_parser/src/lexer/mod.rs index b5e917dbf..0337f8eb0 100644 --- a/deps/swc/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/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. `