diff --git a/Cargo.lock b/Cargo.lock index 372b648b..8e338ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,29 +2,20 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli 0.29.0", -] - [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.1", + "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -60,9 +51,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ambient-authority" @@ -85,11 +76,17 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -102,49 +99,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "argon2" @@ -184,20 +181,20 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -214,23 +211,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.22.0", - "cc", + "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -293,12 +290,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] @@ -316,9 +313,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -328,9 +325,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -408,9 +405,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -428,6 +425,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbindgen" version = "0.27.0" @@ -437,23 +440,24 @@ dependencies = [ "heck 0.4.1", "indexmap", "log", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.90", "tempfile", "toml", ] [[package]] name = "cc" -version = "1.1.10" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -519,10 +523,37 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", "stacker", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -563,16 +594,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cliclack" @@ -605,9 +636,9 @@ checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -668,18 +699,18 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -717,8 +748,8 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli 0.31.1", - "hashbrown", + "gimli", + "hashbrown 0.14.5", "log", "regalloc2", "rustc-hash", @@ -799,6 +830,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -858,6 +925,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -876,7 +949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -958,9 +1031,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1012,9 +1085,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "email-encoding" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" +checksum = "ea3d894bbbab314476b265f9b2d46bf24b123a36dd0e96b06a1b49545b9d9dcc" dependencies = [ "base64 0.22.1", "memchr", @@ -1032,6 +1105,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1040,9 +1119,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -1061,12 +1140,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1080,9 +1159,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "event-listener" @@ -1108,9 +1187,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -1118,9 +1197,9 @@ dependencies = [ [[package]] name = "extism" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7dbc26280a335a1cb5547f99a6459b58d72568bf760306633a16c02826e88f" +checksum = "f03e2cac5668dead4088aa9da25c9985f1a1b72edd3e31b201d2c044647b56f2" dependencies = [ "anyhow", "cbindgen", @@ -1144,9 +1223,9 @@ dependencies = [ [[package]] name = "extism-convert" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a62907d60afa3fc0dfc273833fcb5bb68771f771ce137da1dab7916d856ce9" +checksum = "a33423cbb1226c483f47a9cad883ee804caf45d8b0d2e5c03cd33d9e43ca5561" dependencies = [ "anyhow", "base64 0.22.1", @@ -1160,22 +1239,22 @@ dependencies = [ [[package]] name = "extism-convert-macros" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd75ee443312a8cd7a402e29fb369c1a2b72f532fd7cdaf34f3023e5d8963d5" +checksum = "8bb132f6e20aab7e8eb3715e26ff8893c611abba73242e00771d740a0fbcb384" dependencies = [ "manyhow", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "extism-manifest" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8203adde7bb7a632122e387564314c804793773e9a701fd8cbb6b2c5940a6640" +checksum = "6d9c8b50558af0a75ce08b8ef90a37fb018d99cc99a5f7365c33ba008afdbfb4" dependencies = [ "base64 0.22.1", "serde", @@ -1202,9 +1281,9 @@ checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fd-lock" @@ -1219,9 +1298,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -1231,9 +1310,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1241,9 +1320,9 @@ dependencies = [ [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -1254,6 +1333,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1300,9 +1385,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1331,9 +1416,9 @@ checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1352,9 +1437,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1451,12 +1536,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - [[package]] name = "gimli" version = "0.31.1" @@ -1478,8 +1557,8 @@ dependencies = [ "gix-date", "gix-utils", "itoa", - "thiserror", - "winnow 0.6.18", + "thiserror 1.0.69", + "winnow 0.6.20", ] [[package]] @@ -1498,34 +1577,34 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.69", "unicode-bom", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] name = "gix-config-value" -version = "0.14.8" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c" +checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" dependencies = [ "bitflags 2.6.0", "bstr", "gix-path", "libc", - "thiserror", + "thiserror 2.0.3", ] [[package]] name = "gix-date" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c84b7af01e68daf7a6bb8bb909c1ff5edb3ce4326f1f43063a5a96d3c3c8a5" +checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d" dependencies = [ "bstr", "itoa", "jiff", - "thiserror", + "thiserror 2.0.3", ] [[package]] @@ -1573,7 +1652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" dependencies = [ "faster-hex", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1584,7 +1663,7 @@ checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1602,21 +1681,21 @@ dependencies = [ "gix-validate", "itoa", "smallvec", - "thiserror", - "winnow 0.6.18", + "thiserror 1.0.69", + "winnow 0.6.20", ] [[package]] name = "gix-path" -version = "0.10.11" +version = "0.10.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af" +checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" dependencies = [ "bstr", "gix-trace", "home", "once_cell", - "thiserror", + "thiserror 2.0.3", ] [[package]] @@ -1636,15 +1715,15 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror", - "winnow 0.6.18", + "thiserror 1.0.69", + "winnow 0.6.20", ] [[package]] name = "gix-sec" -version = "0.10.8" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f" +checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" dependencies = [ "bitflags 2.6.0", "gix-path", @@ -1667,15 +1746,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b" +checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" [[package]] name = "gix-utils" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" +checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" dependencies = [ "fastrand", "unicode-normalization", @@ -1683,12 +1762,12 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2badbb64e57b404593ee26b752c26991910fd0d81fe6f9a71c1a8309b6c86" +checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", - "thiserror", + "thiserror 2.0.3", ] [[package]] @@ -1699,22 +1778,22 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "h2" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1729,13 +1808,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "halfbrown" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8588661a8607108a5ca69cab034063441a0413a0b041c13618a7dd348021ef6f" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1749,13 +1838,22 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1772,9 +1870,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex-simd" @@ -1831,9 +1929,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1917,9 +2015,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2051,9 +2149,9 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2070,37 +2168,36 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "idna" -version = "1.0.2" +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", - "smallvec", - "utf8_iter", ] [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -2120,33 +2217,33 @@ dependencies = [ "normalize-path", "project-origins", "radix_trie", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "indexmap" -version = "2.3.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", "serde", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width 0.1.11", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -2195,15 +2292,6 @@ dependencies = [ "unicode-width 0.1.11", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-extras" version = "0.18.3" @@ -2222,9 +2310,20 @@ checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] [[package]] name = "is_terminal_polyfill" @@ -2232,6 +2331,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -2241,11 +2349,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "ittapi" @@ -2269,9 +2386,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a45489186a6123c128fdf6016183fcfab7113e1820eb813127e036e287233fb" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" dependencies = [ "jiff-tzdb-platform", "windows-sys 0.59.0", @@ -2303,10 +2420,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2325,6 +2443,14 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "jsx_parser" +version = "0.12.30" +dependencies = [ + "criterion", + "regex", +] + [[package]] name = "kqueue" version = "1.0.8" @@ -2372,7 +2498,7 @@ dependencies = [ "futures-io", "futures-util", "httpdate", - "idna 1.0.2", + "idna", "mime", "native-tls", "nom", @@ -2386,15 +2512,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" @@ -2436,9 +2562,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2467,25 +2593,25 @@ dependencies = [ [[package]] name = "manyhow" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b8ea82a2287fe7b26aea89c0c02957886d7e97eabffc1f9d2031feaa6f82e6" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" dependencies = [ "manyhow-macros", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "manyhow-macros" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36e8d9b4095531e43de72ed424f0f4a98cba40f7e5a99366f9818769489272" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" dependencies = [ "proc-macro-utils", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", ] [[package]] @@ -2520,34 +2646,34 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "cfg-if", "miette-derive", - "thiserror", + "thiserror 1.0.69", "unicode-width 0.1.11", ] [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2598,11 +2724,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -2619,11 +2745,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", @@ -2792,12 +2917,12 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown", + "hashbrown 0.15.2", "indexmap", "memchr", ] @@ -2808,6 +2933,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2835,9 +2966,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -2848,9 +2979,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] @@ -2893,9 +3024,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -2983,9 +3114,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -2995,9 +3126,37 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plotters" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] [[package]] name = "poly1305" @@ -3012,18 +3171,19 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "postcard" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ "cobs", - "embedded-io", + "embedded-io 0.4.0", + "embedded-io 0.6.1", "serde", ] @@ -3054,11 +3214,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] @@ -3068,8 +3228,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", "version_check", ] @@ -3080,19 +3240,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "version_check", ] [[package]] name = "proc-macro-utils" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "smallvec", ] @@ -3107,9 +3267,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3162,17 +3322,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "itertools 0.13.0", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "psm" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] @@ -3210,6 +3370,7 @@ dependencies = [ "colored", "dotenv", "inquire", + "jsx_parser", "lazy_static", "mime_guess", "once_cell", @@ -3304,9 +3465,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", @@ -3315,39 +3476,43 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring", "rustc-hash", "rustls", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3361,11 +3526,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", ] [[package]] @@ -3446,22 +3611,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3479,9 +3644,9 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -3490,7 +3655,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12908dbeb234370af84d0579b9f68258a0f67e201412dd9a2814e6f45b2fc0f0" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", "log", "rustc-hash", "slice-group-by", @@ -3505,7 +3670,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -3520,9 +3685,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -3674,10 +3839,10 @@ dependencies = [ "phf_shared", "proc-macro-crate 1.3.1", "proc-macro-error", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "rquickjs-core", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3713,9 +3878,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" @@ -3734,9 +3899,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -3749,11 +3914,10 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] @@ -3762,6 +3926,9 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -3813,11 +3980,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3841,9 +4008,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -3882,9 +4049,9 @@ version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -3901,9 +4068,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -3955,6 +4122,12 @@ dependencies = [ "dirs", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -4000,9 +4173,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple_asn1" @@ -4012,7 +4185,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -4069,9 +4242,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4097,15 +4270,15 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -4137,27 +4310,27 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -4168,9 +4341,9 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -4228,8 +4401,8 @@ checksum = "bf0fb8bfdc709786c154e24a66777493fb63ae97e3036d914c8666774c477069" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -4247,9 +4420,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -4280,22 +4453,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -4349,6 +4542,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -4373,7 +4576,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -4388,9 +4591,9 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -4416,9 +4619,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -4427,9 +4630,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -4471,17 +4674,6 @@ dependencies = [ "winnow 0.5.40", ] -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.22" @@ -4492,20 +4684,20 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -4515,20 +4707,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "tracing-bunyan-formatter" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373" +checksum = "2d637245a0d8774bd48df6482e086c59a8b5348a910c3b0579354045a9d82411" dependencies = [ "ahash", "gethostname", @@ -4544,9 +4736,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -4576,9 +4768,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -4586,9 +4778,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -4607,9 +4799,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" [[package]] name = "try-lock" @@ -4625,18 +4817,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bom" @@ -4646,9 +4829,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -4658,18 +4841,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" @@ -4691,9 +4874,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -4729,12 +4912,12 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", ] @@ -4785,9 +4968,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-trait" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaa56177466248ba59d693a048c0959ddb67f1151b963f904306312548cf392" +checksum = "9170e001f458781e92711d2ad666110f153e4e50bfd5cbd02db6547625714187" dependencies = [ "float-cmp", "halfbrown", @@ -4857,7 +5040,7 @@ dependencies = [ "once_cell", "rustix", "system-interface", - "thiserror", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle", @@ -4866,69 +5049,71 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ - "quote 1.0.36", + "quote 1.0.37", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-encoder" @@ -4941,12 +5126,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.220.0" +version = "0.221.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf48234b389415b226a4daef6562933d38c7b28a8b8f64c5c4130dad1561ab7" +checksum = "de35b6c3ef1f53ac7a31b5e69bc00f1542ea337e7e7162dc34c68b537ff82690" dependencies = [ "leb128", - "wasmparser 0.220.0", + "wasmparser 0.221.0", ] [[package]] @@ -4957,7 +5142,7 @@ checksum = "b09e46c7fceceaa72b2dd1a8a137ea7fd8f93dfaa69806010a709918e496c5dc" dependencies = [ "ahash", "bitflags 2.6.0", - "hashbrown", + "hashbrown 0.14.5", "indexmap", "semver", "serde", @@ -4965,12 +5150,13 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.220.0" +version = "0.221.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" +checksum = "8659e755615170cfe20da468865c989da78c5da16d8652e69a75acda02406a92" dependencies = [ "bitflags 2.6.0", "indexmap", + "semver", ] [[package]] @@ -4990,7 +5176,7 @@ version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e762e163fd305770c6c341df3290f0cabb3c264e7952943018e9a1ced8d917" dependencies = [ - "addr2line 0.24.2", + "addr2line", "anyhow", "async-trait", "bitflags 2.6.0", @@ -4999,8 +5185,8 @@ dependencies = [ "cfg-if", "encoding_rs", "fxprof-processed-profile", - "gimli 0.31.1", - "hashbrown", + "gimli", + "hashbrown 0.14.5", "indexmap", "ittapi", "libc", @@ -5077,9 +5263,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61a4b5ce2ad9c15655e830f0eac0c38b8def30c74ecac71f452d3901e491b68" dependencies = [ "anyhow", - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -5104,13 +5290,13 @@ dependencies = [ "cranelift-entity", "cranelift-frontend", "cranelift-native", - "gimli 0.31.1", - "itertools", + "gimli", + "itertools 0.12.1", "log", "object", "smallvec", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser 0.218.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -5126,7 +5312,7 @@ dependencies = [ "cpp_demangle", "cranelift-bitset", "cranelift-entity", - "gimli 0.31.1", + "gimli", "indexmap", "log", "object", @@ -5194,9 +5380,9 @@ version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8efb877c9e5e67239d4553bb44dd2a34ae5cfb728f3cf2c5e64439c6ca6ee7" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5207,7 +5393,7 @@ checksum = "4f7a267367382ceec3e7f7ace63a63b83d86f4a680846743dead644e10f08150" dependencies = [ "anyhow", "cranelift-codegen", - "gimli 0.31.1", + "gimli", "object", "target-lexicon", "wasmparser 0.218.0", @@ -5239,24 +5425,24 @@ dependencies = [ [[package]] name = "wast" -version = "220.0.0" +version = "221.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e708c8de08751fd66e70961a32bae9d71901f14a70871e181cb8461a3bb3165" +checksum = "9d8eb1933d493dd07484a255c3f52236123333f5befaa3be36182a50d393ec54" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width 0.2.0", - "wasm-encoder 0.220.0", + "wasm-encoder 0.221.0", ] [[package]] name = "wat" -version = "1.220.0" +version = "1.221.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4f1d7d59614ba690541360102b995c4eb1b9ed373701d5102cc1a968b1c5a3" +checksum = "c813fd4e5b2b97242830b56e7b7dc5479bc17aaa8730109be35e61909af83993" dependencies = [ - "wast 220.0.0", + "wast 221.0.0", ] [[package]] @@ -5277,7 +5463,7 @@ dependencies = [ "once_cell", "process-wrap", "project-origins", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "watchexec-events", @@ -5304,7 +5490,7 @@ checksum = "be07d7855a3617d996ce0c7df4b6232159c526634dff668dd95491c22a9a7262" dependencies = [ "miette", "nix 0.29.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5324,9 +5510,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -5334,9 +5530,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -5350,7 +5546,7 @@ dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle-macro", @@ -5365,10 +5561,10 @@ checksum = "28ff23bed568b335dac6a324b8b167318a0c60555199445fcc89745a5eb42452" dependencies = [ "anyhow", "heck 0.5.0", - "proc-macro2 1.0.86", - "quote 1.0.36", + "proc-macro2 1.0.92", + "quote 1.0.37", "shellexpand", - "syn 2.0.87", + "syn 2.0.90", "witx", ] @@ -5378,9 +5574,9 @@ version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f13be83541aa0b033ac5ec8a8b59c9a8d8b32305845b8466dd066e722cb0004" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wiggle-generate", ] @@ -5423,7 +5619,7 @@ checksum = "07ab957fc71a36c63834b9b51cc2e087c4260d5ff810a5309ab99f7fbeb19567" dependencies = [ "anyhow", "cranelift-codegen", - "gimli 0.31.1", + "gimli", "regalloc2", "smallvec", "target-lexicon", @@ -5469,9 +5665,9 @@ version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5480,9 +5676,9 @@ version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5683,9 +5879,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -5714,7 +5910,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", "wasmparser 0.218.0", ] @@ -5726,7 +5922,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror", + "thiserror 1.0.69", "wast 35.0.2", ] @@ -5744,9 +5940,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -5756,13 +5952,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "synstructure", ] @@ -5782,29 +5978,29 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "synstructure", ] @@ -5823,9 +6019,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5845,9 +6041,9 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.36", - "syn 2.0.87", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 83ad339e..c072eb7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [workspace] members = [ - "crates/server", "crates/cli", + "crates/jsx_parser", "crates/runtime", + "crates/server", ] resolver = "2" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 83c515f8..3b4b2670 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,6 +20,7 @@ cliclack = "0.3" colored = "2.1" dotenv = "0.15" inquire = "0.7" +jsx_parser = { path = "../jsx_parser" } lazy_static = "1.5" mime_guess = "2.0" once_cell = "1.20" diff --git a/crates/cli/src/commands/function.rs b/crates/cli/src/commands/function.rs index 57db93ba..d5ee5996 100644 --- a/crates/cli/src/commands/function.rs +++ b/crates/cli/src/commands/function.rs @@ -12,6 +12,8 @@ use std::{ #[allow(unused_imports)] use anyhow::anyhow; +#[allow(unused_imports)] +use jsx_parser::jsx_precompile::jsx_precompile; use anyhow::Result; use colored::Colorize; @@ -268,8 +270,12 @@ pub fn esbuild(function_path: &str) -> Result> { ]; let config_esbuild = &CONFIG.esbuild; + let is_jsx_file = path.ends_with(".jsx") || path.ends_with(".tsx"); + let mut has_jsx_preserve = false; for (key, value) in config_esbuild { + has_jsx_preserve = has_jsx_preserve || is_jsx_file && key == "jsx" && value == "preserve"; + let flag = if value.is_empty() { format!("--{}", key) } else { @@ -330,7 +336,7 @@ pub fn esbuild(function_path: &str) -> Result> { }; let bundle_path = format!("{}/{}", out_dir, function_path.split('/').last().unwrap()); - let re = Regex::new(r"(\.tsx|\.ts)$").unwrap(); + let re = Regex::new(r"(\.jsx|\.tsx|\.ts)$").unwrap(); let bundle_path = re.replace(&bundle_path, ".js").to_string(); let function = fs::read_to_string(&bundle_path)?; @@ -347,6 +353,22 @@ pub fn esbuild(function_path: &str) -> Result> { let re_export = Regex::new(r"export\{([\S]+) as handleRequest\};\n$").unwrap(); let function = re_export.replace(&function, "globalThis.___handleRequest = $1;".to_string()); + let function = if has_jsx_preserve { + match jsx_precompile(&function) { + Ok(f) => f, + Err(e) => { + eprintln!( + "{} Error precompiling the JSX from the function {function_path} - {}", + String::from('●').red(), + e + ); + exit(1) + } + } + } else { + function.to_string() + }; + fs::remove_file(bundle_path)?; Ok(function.as_bytes().to_vec()) @@ -358,6 +380,26 @@ mod tests { use super::*; + struct TestCleanup { + file_path: &'static str, + } + + impl Drop for TestCleanup { + fn drop(&mut self) { + std::fs::remove_file(self.file_path).unwrap(); + } + } + + impl TestCleanup { + const NO_VALID_NAME: Self = Self { + file_path: "../../.tests/src/functions/post.no_index.js", + }; + + const INVALID_PREFIX: Self = Self { + file_path: "../../.tests/src/functions/invalid_prefix.js", + }; + } + #[test] #[should_panic( expected = r#"The file "../../.tests/src/functions/post.not_exist.js" doesn't exists"# @@ -369,20 +411,12 @@ mod tests { function_builder(&path).unwrap(); } - struct TestFunctionBuilderFileNameNoValidName; - - impl Drop for TestFunctionBuilderFileNameNoValidName { - fn drop(&mut self) { - std::fs::remove_file("../../.tests/src/functions/post.no_index.js").unwrap(); - } - } - #[test] #[should_panic( expected = r#"The file "../../.tests/src/functions/post.no_index.js" doesn't have a valid name. It should be "index" or "[slug]". Ex. "get.index.(js|jsx|ts|tsx)" or "post.[slug].(js|jsx|ts|tsx)""# )] fn test_function_builder_file_name_no_valid_name() { - let _after = TestFunctionBuilderFileNameNoValidName; + let _after = TestCleanup::NO_VALID_NAME; let dir = "../../.tests/src/functions".to_string(); let path = format!("{dir}/post.no_index.js"); @@ -394,20 +428,12 @@ mod tests { function_builder(&path).unwrap(); } - struct TestFunctionBuilderFileNameInvalidPrefix; - - impl Drop for TestFunctionBuilderFileNameInvalidPrefix { - fn drop(&mut self) { - std::fs::remove_file("../../.tests/src/functions/invalid_prefix.js").unwrap(); - } - } - #[test] #[should_panic( expected = r#"The file "../../.tests/src/functions/invalid_prefix.js" doesn't have a valid name. It should be "index" or "[slug]". Ex. "get.index.(js|jsx|ts|tsx)" or "post.[slug].(js|jsx|ts|tsx)""# )] fn test_function_builder_invalid_prefix() { - let _after = TestFunctionBuilderFileNameInvalidPrefix; + let _after = TestCleanup::INVALID_PREFIX; let dir = "../../.tests/src/functions".to_string(); let path = format!("{dir}/invalid_prefix.js"); diff --git a/crates/jsx_parser/Cargo.toml b/crates/jsx_parser/Cargo.toml new file mode 100644 index 00000000..fb906eb0 --- /dev/null +++ b/crates/jsx_parser/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "jsx_parser" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +regex = "1.11" + +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "jsx_parser" +harness = false +test = false \ No newline at end of file diff --git a/crates/jsx_parser/benches/jsx_parser.rs b/crates/jsx_parser/benches/jsx_parser.rs new file mode 100644 index 00000000..32953033 --- /dev/null +++ b/crates/jsx_parser/benches/jsx_parser.rs @@ -0,0 +1,276 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jsx_parser::jsx_parser::Parser; +use jsx_parser::jsx_precompile::jsx_precompile; + +fn bench_simple_element(c: &mut Criterion) { + let input = "
Hello World
"; + c.bench_function("simple element", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_element_with_attributes(c: &mut Criterion) { + let input = r#"
Content
"#; + c.bench_function("element with attributes", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_nested_elements(c: &mut Criterion) { + let input = r#" +
1 +
+

Title

+ +
+
+ "#; + c.bench_function("nested elements", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_expressions(c: &mut Criterion) { + let input = r#"
{count + 1} items remaining
"#; + c.bench_function("expressions", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_complex_expressions(c: &mut Criterion) { + let input = r#" +
+ {items.map(item => ( +
+ {item.name} + {item.count} +
+ ))} +
+ "#; + c.bench_function("complex expressions", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_fragment(c: &mut Criterion) { + let input = r#" + <> +
First
+
Second
+
Third
+ + "#; + c.bench_function("fragment", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_spread_attributes(c: &mut Criterion) { + let input = r#""#; + c.bench_function("spread attributes", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_conditional_rendering(c: &mut Criterion) { + let input = r#" +
+ {loading ? ( + + ) : ( + + )} +
+ "#; + c.bench_function("conditional rendering", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_large_component(c: &mut Criterion) { + let input = r#" +
+
+

{title || "Default Title"}

+ + {user ? ( +
+ User avatar + {user.name} + +
+ ) : ( + + )} +
+
+ {loading ? ( +
+ +
+ ) : error ? ( + + ) : ( + <>{children} + )} +
+
+

© {currentYear} My Application

+
+
+ "#; + c.bench_function("large component", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_many_siblings(c: &mut Criterion) { + let input = r#" +
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
+ "#; + c.bench_function("many siblings", |b| { + b.iter(|| { + let mut parser = Parser::new(input); + parser.parse().unwrap() + }) + }); +} + +fn bench_jsx_precompile(c: &mut Criterion) { + let input = r#" +
+
+

{title || "Default Title"}

+ + {user ? ( +
+ User avatar + {user.name} + +
+ ) : ( + + )} +
+
+ {loading ? ( +
+ +
+ ) : error ? ( + + ) : ( + <>{children} + )} +
+
+

© {currentYear} My Application

+
+
+ "#; + c.bench_function("jsx precompile large component", |b| { + b.iter(|| { + jsx_precompile(input).unwrap() + }) + }); +} + +criterion_group!(benches, + bench_simple_element, + bench_element_with_attributes, + bench_nested_elements, + bench_expressions, + bench_complex_expressions, + bench_fragment, + bench_spread_attributes, + bench_conditional_rendering, + bench_large_component, + bench_many_siblings, + bench_jsx_precompile +); + +criterion_main!(benches); \ No newline at end of file diff --git a/crates/jsx_parser/src/jsx_extractor.rs b/crates/jsx_parser/src/jsx_extractor.rs new file mode 100644 index 00000000..ef233e77 --- /dev/null +++ b/crates/jsx_parser/src/jsx_extractor.rs @@ -0,0 +1,898 @@ +/// A location of a JSX element in source code +#[derive(Debug, Clone, PartialEq)] +pub struct JSXLocation { + start: usize, + end: usize, + pub content: String, +} + +/// Main JSX extractor that parses JSX elements from source code +#[derive(Debug)] +pub struct JSXExtractor { + input: String, + pub locations: Vec, + fragment_ranges: Vec<(usize, usize)>, +} + +const INITIAL_DEPTH: usize = 1; // Initial depth for JSX element nesting +const INITIAL_POS_INCREMENT: usize = 1; // Initial position increment +const DOUBLE_QUOTE: char = '"'; // Character to denote double quote +const SINGLE_QUOTE: char = '\''; // Character to denote single quote +const BACKSLASH: char = '\\'; // Character to denote backslash +const OPEN_ANGLE_BRACKET: char = '<'; // Character to denote open angle bracket +const CLOSE_ANGLE_BRACKET: char = '>'; // Character to denote close angle bracket +const CLOSE_BRACKET_BYTE: u8 = b'>'; // Character to denote close bracket as byte +const UNDERSCORE: char = '_'; // Character to denote underscore +const DOLLAR_SIGN: char = '$'; // C +const OPEN_CURLY_BRACE: char = '{'; // Character to denote opening curly brace +const SLASH: char = '/'; // Character to denote slash +const SLASH_BYTE: u8 = b'/'; // Character to denote slash as bytes +const EQUALS: char = '='; // Character to denote equals sign +const FRAGMENT_START: &str = "<>"; // Fragment start indicator +const FRAGMENT_END: &str = ""; // Fragment end indicator +const SELF_CLOSING_END: &str = "/>"; // Character sequence to denote self-closing element + +impl JSXExtractor { + /// Creates a new JSXExtractor instance + pub fn new(input: String) -> Self { + Self { + input, + locations: Vec::new(), + fragment_ranges: Vec::new(), + } + } + + /// Main extraction method that handles JSX elements + pub fn extract(&mut self) -> Result, regex::Error> { + self.extract_elements(); + self.locations.sort_by_key(|loc| loc.start); + + Ok(self + .locations + .iter() + .map(|loc| loc.content.clone()) + .collect()) + } + + fn extract_elements(&mut self) { + let mut pos = 0; + while pos < self.input.len() { + if let Some(start) = self.find_element_start(pos) { + if let Some(end) = self.find_element_end(start) { + let content = &self.input[start..end]; + + self.fragment_ranges.push((start, end)); + self.locations.push(JSXLocation { + start, + end, + content: content.to_string(), + }); + pos = end; + } else { + pos += INITIAL_POS_INCREMENT; + } + } else { + break; + } + } + } + + fn find_element_start(&self, from: usize) -> Option { + let input = &self.input[from..]; + let mut chars = input.char_indices().peekable(); + + while let Some((i, c)) = chars.next() { + if c == OPEN_ANGLE_BRACKET { + if let Some(&(_, next)) = chars.peek() { + // Handle fragments (empty element name) + if next == CLOSE_ANGLE_BRACKET { + return Some(from + i); + } + + // Handle regular elements - first character must be a letter or underscore or a dollar sign + if !next.is_ascii_alphabetic() && next != UNDERSCORE && next != DOLLAR_SIGN { + continue; + } + + chars.next(); // consume the first valid character + + // Look ahead to ensure the element name is valid + while let Some(&(_, c)) = chars.peek() { + if c.is_whitespace() || c == CLOSE_ANGLE_BRACKET || c == OPEN_CURLY_BRACE { + return Some(from + i); + } + // Only continue if we find valid element name characters + if !c.is_alphanumeric() && c != UNDERSCORE && c != DOLLAR_SIGN { + break; + } + chars.next(); + } + } + } + } + None + } + + fn find_element_end(&self, start: usize) -> Option { + let mut depth = INITIAL_DEPTH; + let mut pos = start + INITIAL_POS_INCREMENT; + let mut in_string = false; + let mut string_char = None; + let mut escaped = false; + let is_fragment = self.input[start..].starts_with(FRAGMENT_START); + + while pos < self.input.len() { + let c = self.input[pos..].chars().next()?; + + // Handle string context + if in_string { + if !escaped && Some(c) == string_char { + in_string = false; + } + escaped = c == BACKSLASH && !escaped; + pos += c.len_utf8(); + continue; + } + + // Start string context only if character is after an equals sign in an attribute + if (c == DOUBLE_QUOTE || c == SINGLE_QUOTE) + && self.is_in_attribute_context(&self.input[..pos]) + { + in_string = true; + string_char = Some(c); + pos += c.len_utf8(); + continue; + } + + match c { + OPEN_ANGLE_BRACKET => { + let next_pos = pos + 1; + if next_pos < self.input.len() { + if self.input.as_bytes()[next_pos] == SLASH_BYTE { + // Found a closing tag + if self.input[pos..].starts_with(FRAGMENT_END) { + depth -= 1; + if depth == 0 { + return Some(pos + FRAGMENT_END.len()); + } + pos += FRAGMENT_END.len() - 1; + continue; + } else { + depth -= 1; + if depth == 0 { + return self.find_closing_bracket(next_pos); + } + } + } else if self.is_valid_jsx_start(&self.input[pos..]) + || self.input[pos..].starts_with(FRAGMENT_START) + { + depth += 1; + } + } + } + SLASH => { + if pos > 0 && self.input.as_bytes()[pos - 1] == OPEN_ANGLE_BRACKET as u8 { + // Handle self-closing tags + if is_fragment && self.input[pos..].starts_with(SELF_CLOSING_END) { + depth -= 1; + if depth == 0 { + return Some(pos + 2); + } + pos += 2; + continue; + } + } else if pos + 1 < self.input.len() + && self.input.as_bytes()[pos + 1] == CLOSE_BRACKET_BYTE + { + // Handle self-closing tags with /> + depth -= 1; + if depth == 0 { + return Some(pos + 2); + } + pos += 1; + } + } + _ => {} + } + pos += c.len_utf8(); + } + None + } + + #[inline] + fn is_in_attribute_context(&self, input: &str) -> bool { + let chars = input.chars().rev(); + let mut found_equals = false; + + for c in chars { + match c { + EQUALS => { + found_equals = true; + break; + } + CLOSE_ANGLE_BRACKET => return false, + c if !c.is_whitespace() && c != DOUBLE_QUOTE && c != SINGLE_QUOTE => continue, + _ => continue, + } + } + found_equals + } + + #[inline] + fn find_closing_bracket(&self, mut pos: usize) -> Option { + while pos < self.input.len() { + if self.input.as_bytes()[pos] == CLOSE_BRACKET_BYTE { + return Some(pos + INITIAL_POS_INCREMENT); + } + pos += INITIAL_POS_INCREMENT; + } + None + } + + #[inline] + fn is_valid_jsx_start(&self, input: &str) -> bool { + input + .chars() + .nth(1) + .map(|c| c.is_ascii_alphabetic() || c == UNDERSCORE || c == DOLLAR_SIGN) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_jsx_doctype_handling() { + let input = r#"Test"#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!(locations, vec!["Test".to_string()]); + } + + #[test] + fn test_jsx_extractor_extract_simple_jsx() { + let input = r#" + function App() { + return
Hello World
; + } + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!(locations, vec!["
Hello World
".to_string()]); + } + + #[test] + fn test_jsx_extractor_copy_button_and_code() { + let input = r#" + + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec!["".to_string()] + ); + } + + #[test] + fn test_jsx_extractor_with_apostrophe() { + let input = "

We don't share it with third parties

"; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec!["

We don't share it with third parties

".to_string()] + ); + } + + #[test] + fn test_jsx_extractor_with_web_component() { + let input = r#" + function App() { + return
Hello World
; + } + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec!["
Hello World
".to_string()] + ); + } + + #[test] + fn test_jsx_extractor_components_with_underscore_and_dollar() { + let input = r#" + const element = ( +
+ <_CustomComponent> + Inside underscore component + + <$DollarComponent prop={value}> +

Inside dollar component

+ + <_NestedComponent> + <$InnerComponent> +
Nested special components
+ + +
+ ); + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![r#"
+ <_CustomComponent> + Inside underscore component + + <$DollarComponent prop={value}> +

Inside dollar component

+ + <_NestedComponent> + <$InnerComponent> +
Nested special components
+ + +
"# + .to_string()] + ); + } + + #[test] + fn test_jsx_extractor_extract_jsx_with_props() { + let input = r#" + const element = ; + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![r#""# + .to_string()] + ); + } + + #[test] + fn test_jsx_extractor_with_spread_operator_and_template_literals() { + let input = + r#"{e}"#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![ + r#"{e}"# + .to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_extract_self_closing_tag() { + let input = r#"const img = Test;"#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![r#"Test"#.to_string()] + ); + } + + #[test] + fn test_jsx_extractor_extract_fragment() { + let input = r#" + let element = <> +
First
+
Second
+ ; + function Fragment() { + return <> +
First
+
Second
+ ; + } + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec!["<>\n
First
\n
Second
\n ".to_string() + , + "<>\n
First
\n
Second
\n ".to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_extract_complex_jsx() { + let input = r#" + function ComplexComponent() { + return ( +
+
+ {loading ? ( + + ) : ( +

{title}

+ )} +
+ +
+ ); + } + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec!["
\n
\n {loading ? (\n \n ) : (\n

{title}

\n )}\n
\n \n
".to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_nested_expressions() { + let input = r#" +
+ {(() => { + const x = { y: 1 }; + return x.y; + })()} +
+ "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![r#"
+ {(() => { + const x = { y: 1 }; + return x.y; + })()} +
"# + .to_string()] + ); + } + + #[test] + fn test_jsx_extractor_invalid_jsx() { + let input = r#" + const x = < 5; + const y = This is valid; + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!(locations, vec!["
This is valid
".to_string()]); + } + + #[test] + fn test_jsx_extractor_string_escaping() { + let input = r#" + const element =
+ Text +
; + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![ + r#"
+ Text +
"# + .to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_mixed_fragments_and_elements() { + let input = r#" + <> +
First
+ <> + Nested +

Fragment

+ +
Last
+ +
Outside fragment
+ "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![ + r#"<> +
First
+ <> + Nested +

Fragment

+ +
Last
+ "# + .to_string(), + r#"
Outside fragment
"#.to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_nested_fragments_() { + let input = r#" + <> + <> + <> + F + + + + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![ + "<>\n <>\n <>\n F\n \n \n ".to_string() + ] + ); + } + + #[test] + fn test_jsx_extractor_attribute_edge_cases() { + let input = r#" +
+ + +
+ ) : ( + + )} + +
+ {loading ? ( +
+ +
+ ) : error ? ( + + ) : ( + <>{children} + )} +
+
+

© {currentYear} My Application

+
+ ) + ;"#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!( + locations, + vec![r#"
+
+

{title || "Default Title"}

+ + {user ? ( +
+ User avatar + {user.name} + +
+ ) : ( + + )} +
+
+ {loading ? ( +
+ +
+ ) : error ? ( + + ) : ( + <>{children} + )} +
+
+

© {currentYear} My Application

+
+
"#.to_string()] + ); + } + + #[test] + fn test_jsx_extractor_invalid_jsx_with_broken_syntax() { + let input = r#" + const code = ; + const valid =
Valid element
; + "#; + let mut extractor = JSXExtractor::new(String::from(input)); + let locations = extractor + .extract() + .expect("Failed to extract JSX locations"); + + assert_eq!(locations, vec!["
Valid element
".to_string()]); + } +} diff --git a/crates/jsx_parser/src/jsx_parser.rs b/crates/jsx_parser/src/jsx_parser.rs new file mode 100644 index 00000000..88226612 --- /dev/null +++ b/crates/jsx_parser/src/jsx_parser.rs @@ -0,0 +1,1587 @@ +use std::iter::Peekable; +use std::str::Chars; + +#[derive(Debug, PartialEq)] +pub enum JSXNode { + Element { + tag: String, + attributes: Vec, + children: Vec, + }, + Fragment { + children: Vec, + }, + Text(String), + Expression(String), +} + +#[derive(Debug, PartialEq)] +pub struct JSXAttribute { + pub name: String, + pub value: Option, +} + +#[derive(Debug, PartialEq)] +pub enum JSXAttributeValue { + String(String), + Expression(String), +} + +// Token characters +const LEFT_ANGLE: char = '<'; +const RIGHT_ANGLE: char = '>'; +const FORWARD_SLASH: char = '/'; +const LEFT_BRACE: char = '{'; +const RIGHT_BRACE: char = '}'; +const EQUALS: char = '='; +const DOUBLE_QUOTE: char = '"'; +const UNDERSCORE: char = '_'; +const DOLLAR_SIGN: char = '$'; +const HYPHEN: char = '-'; +const DOT: char = '.'; + +// Error messages +const ERR_EXPECT_JSX: &str = "Expected JSX element or fragment"; +const ERR_EXPECT_CLOSE_ANGLE: &str = "Expected >"; +const ERR_EXPECT_CLOSE_SLASH: &str = "Expected > after /"; +const ERR_FRAGMENT_CLOSE: &str = "Expected > for fragment closing tag"; +const ERR_MISMATCHED_TAG: &str = "Mismatched closing tag: expected {}, found {}"; +const ERR_UNCLOSED_TAG: &str = "Unclosed tag: {}"; +const ERR_EXPECT_IDENTIFIER: &str = "Expected identifier"; +const ERR_UNTERMINATED_STRING: &str = "Unterminated string literal"; +const ERR_EXPECT_STRING_OR_EXPR: &str = "Expected string or expression"; +const ERR_UNCLOSED_EXPRESSION: &str = "Unclosed expression"; +const ERR_EXPECT_SPREAD_OPERATOR: &str = "Expected identifier after spread operator"; + +pub struct Parser<'a> { + chars: Peekable>, + pos: usize, +} + +impl<'a> Parser<'a> { + pub fn new(input: &'a str) -> Self { + Self { + chars: input.chars().peekable(), + pos: 0, + } + } + + pub fn parse(&mut self) -> Result { + self.skip_whitespace(); + if self.peek() == Some(LEFT_ANGLE) { + if self.peek_n(1) == Some(RIGHT_ANGLE) { + self.parse_fragment() + } else { + self.parse_element() + } + } else { + Err(ERR_EXPECT_JSX.to_string()) + } + } + + fn parse_element(&mut self) -> Result { + // Consume < + self.bump(); + self.skip_whitespace(); + + // Parse tag name + let tag = self.parse_identifier()?; + self.skip_whitespace(); + + // Parse attributes + let attributes = self.parse_attributes()?; + self.skip_whitespace(); + + // Handle self-closing tags + if self.peek() == Some(FORWARD_SLASH) { + self.bump(); + if self.peek() == Some(RIGHT_ANGLE) { + self.bump(); + return Ok(JSXNode::Element { + tag, + attributes, + children: vec![], + }); + } + return Err(ERR_EXPECT_CLOSE_SLASH.to_string()); + } + + // Expect > + if self.peek() != Some(RIGHT_ANGLE) { + return Err(ERR_EXPECT_CLOSE_ANGLE.to_string()); + } + self.bump(); + + // Parse children + let children = self.parse_children(&tag)?; + + Ok(JSXNode::Element { + tag, + attributes, + children, + }) + } + + fn parse_fragment(&mut self) -> Result { + // Consume <> + self.bump(); + self.bump(); + + let children = self.parse_children("fragment")?; + + Ok(JSXNode::Fragment { children }) + } + + fn parse_children(&mut self, parent_tag: &str) -> Result, String> { + let mut children = vec![]; + + // Special handling for script tag - treat everything as text until closing tag + if parent_tag == "script" { + let mut content = String::new(); + + while let Some(c) = self.peek() { + if c == LEFT_ANGLE && self.peek_n(1) == Some(FORWARD_SLASH) { + let peek_pos = self.pos; + let peek_chars = self.chars.clone(); + + self.bump(); // < + self.bump(); // / + self.skip_whitespace(); + + if let Ok(tag) = self.parse_identifier() { + if tag == "script" { + self.skip_whitespace(); + if self.peek() == Some(RIGHT_ANGLE) { + self.bump(); // > + break; + } + } + } + + // Reset position and continue if not + self.pos = peek_pos; + self.chars = peek_chars; + content.push(c); + self.bump(); + } else { + content.push(c); + self.bump(); + } + } + + children.push(JSXNode::Text(content)); + return Ok(children); + } + + loop { + self.skip_whitespace(); + + match self.peek() { + Some(LEFT_ANGLE) => { + if self.peek_n(1) == Some(FORWARD_SLASH) { + // End tag + self.bump(); // < + self.bump(); // / + self.skip_whitespace(); + + if parent_tag == "fragment" { + // For fragments, just expect > + if self.peek() != Some(RIGHT_ANGLE) { + return Err(ERR_FRAGMENT_CLOSE.to_string()); + } + self.bump(); + break; + } else { + // For normal elements, parse the closing tag identifier + let close_tag = self.parse_identifier()?; + if close_tag != parent_tag { + return Err(ERR_MISMATCHED_TAG + .to_string() + .replace("{}", parent_tag) + .replace("{}", &close_tag)); + } + + self.skip_whitespace(); + if self.peek() != Some(RIGHT_ANGLE) { + return Err(ERR_EXPECT_CLOSE_ANGLE.to_string()); + } + self.bump(); + break; + } + } else if self.peek_n(1) == Some(RIGHT_ANGLE) { + // Fragment + children.push(self.parse_fragment()?); + } else { + // Element + children.push(self.parse_element()?); + } + } + Some(LEFT_BRACE) => { + children.push(self.parse_expression()?); + } + Some(_) => { + children.push(self.parse_text()?); + } + None => { + return Err(ERR_UNCLOSED_TAG.to_string().replace("{}", parent_tag)); + } + } + } + + Ok(children) + } + + fn parse_attributes(&mut self) -> Result, String> { + let mut attributes = vec![]; + + while let Some(c) = self.peek() { + if c == RIGHT_ANGLE || c == FORWARD_SLASH { + break; + } + + self.skip_whitespace(); + + // Check for spread operator + if self.check_sequence(&[DOT, DOT, DOT]) { + // Consume the three dots + self.bump(); + self.bump(); + self.bump(); + + let name = match self.parse_identifier() { + Ok(name) => format!("...{}", name), + Err(_) => return Err(ERR_EXPECT_SPREAD_OPERATOR.to_string()), + }; + + attributes.push(JSXAttribute { name, value: None }); + self.skip_whitespace(); + continue; + } + + let name = match self.parse_identifier() { + Ok(name) => name, + Err(_) => { + // Only skip invalid character if it's not part of a spread operator + self.bump(); + String::new() + } + }; + + self.skip_whitespace(); + + let value = if self.peek() == Some(EQUALS) { + self.bump(); + self.skip_whitespace(); + Some(self.parse_attribute_value()?) + } else { + None + }; + + if !name.is_empty() { + attributes.push(JSXAttribute { name, value }); + } + + self.skip_whitespace(); + } + + Ok(attributes) + } + + #[inline] + fn check_sequence(&mut self, chars: &[char]) -> bool { + let mut chars_iter = self.chars.clone(); + + for &expected_char in chars { + match chars_iter.next() { + Some(c) if c == expected_char => continue, + _ => return false, + } + } + + true + } + + fn parse_attribute_value(&mut self) -> Result { + match self.peek() { + Some(DOUBLE_QUOTE) => { + self.bump(); + let mut value = String::new(); + while let Some(c) = self.peek() { + if c == DOUBLE_QUOTE { + break; + } + value.push(c); + self.bump(); + } + if self.peek() != Some(DOUBLE_QUOTE) { + return Err(ERR_UNTERMINATED_STRING.to_string()); + } + self.bump(); + Ok(JSXAttributeValue::String(value)) + } + Some(LEFT_BRACE) => { + self.bump(); + let expr = self.parse_expression_content()?; + Ok(JSXAttributeValue::Expression(expr)) + } + _ => Err(ERR_EXPECT_STRING_OR_EXPR.to_string()), + } + } + + fn parse_expression(&mut self) -> Result { + // Consume { + self.bump(); + let expr = self.parse_expression_content()?; + Ok(JSXNode::Expression(expr)) + } + + fn parse_expression_content(&mut self) -> Result { + let mut content = String::new(); + let mut brace_count = 1; + + while let Some(c) = self.peek() { + match c { + LEFT_BRACE => { + brace_count += 1; + content.push(c); + } + RIGHT_BRACE => { + brace_count -= 1; + if brace_count == 0 { + self.bump(); + return Ok(content); + } + content.push(c); + } + _ => { + content.push(c); + } + } + self.bump(); + } + + Err(ERR_UNCLOSED_EXPRESSION.to_string()) + } + + fn parse_text(&mut self) -> Result { + let mut content = String::new(); + + while let Some(c) = self.peek() { + if c == LEFT_ANGLE || c == LEFT_BRACE { + break; + } + content.push(c); + self.bump(); + } + + Ok(JSXNode::Text(content.to_string())) + } + + fn parse_identifier(&mut self) -> Result { + let mut ident = String::new(); + + if let Some(c) = self.peek() { + if !c.is_ascii_alphabetic() && c != UNDERSCORE && c != DOLLAR_SIGN { + return Err(ERR_EXPECT_IDENTIFIER.to_string()); + } + ident.push(c); + self.bump(); + } + + while let Some(c) = self.peek() { + if c.is_alphanumeric() || c == UNDERSCORE || c == DOLLAR_SIGN || c == HYPHEN { + ident.push(c); + self.bump(); + } else { + break; + } + } + + if ident.is_empty() { + Err(ERR_EXPECT_IDENTIFIER.to_string()) + } else { + Ok(ident) + } + } + + #[inline] + fn peek(&mut self) -> Option { + self.chars.peek().copied() + } + + #[inline] + fn peek_n(&mut self, n: usize) -> Option { + let mut chars = self.chars.clone(); + for _ in 0..n { + chars.next(); + } + chars.next() + } + + #[inline] + fn bump(&mut self) { + if self.chars.next().is_some() { + self.pos += 1; + } + } + + #[inline] + fn skip_whitespace(&mut self) { + while let Some(c) = self.peek() { + if !c.is_whitespace() { + break; + } + self.bump(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_simple_element() { + let mut parser = Parser::new("
Hello
"); + let ast = parser.parse().unwrap(); + + assert_eq!( + ast, + JSXNode::Element { + tag: "div".to_string(), + attributes: vec![], + children: vec![JSXNode::Text("Hello".to_string())] + } + ); + } + + #[test] + fn test_parse_attributes() { + let mut parser = Parser::new(r#"
Hello
"#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { attributes, .. } => { + assert_eq!(attributes.len(), 2); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("container".to_string())) + ); + assert_eq!(attributes[1].name, "id"); + assert_eq!( + attributes[1].value, + Some(JSXAttributeValue::String("main".to_string())) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_expression() { + let mut parser = Parser::new("
{count + 1}
"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { children, .. } => { + assert_eq!(children.len(), 1); + assert_eq!(children[0], JSXNode::Expression("count + 1".to_string())); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_expression_string_template() { + let mut parser = Parser::new("
{`template ${variable}`}
"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { children, .. } => { + assert_eq!(children.len(), 1); + assert_eq!( + children[0], + JSXNode::Expression("`template ${variable}`".to_string()) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_nested_elements() { + let mut parser = Parser::new("
HelloWorld
"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children.len(), 2); + + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "span"); + assert_eq!(children[0], JSXNode::Text("Hello".to_string())); + } + _ => panic!("Expected Element"), + } + + match &children[1] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "b"); + assert_eq!(children[0], JSXNode::Text("World".to_string())); + } + _ => panic!("Expected Element"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_fragment() { + let mut parser = Parser::new("<>World"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Fragment { children } => { + assert_eq!(children.len(), 1); + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "span"); + assert_eq!(children[0], JSXNode::Text("World".to_string())); + } + _ => panic!("Expected Element"), + } + } + _ => panic!("Expected Fragment"), + } + } + + #[test] + fn test_parse_self_closing_tag() { + let mut parser = Parser::new(r#""#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + .. + } => { + assert_eq!(tag, "input"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "type"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("text".to_string())) + ); + assert_eq!(children.len(), 0); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_mixed_content() { + let mut parser = Parser::new("
Hello {name}!
"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { children, .. } => { + assert_eq!(children.len(), 3); + assert_eq!(children[0], JSXNode::Text("Hello ".to_string())); + assert_eq!(children[1], JSXNode::Expression("name".to_string())); + assert_eq!(children[2], JSXNode::Text("!".to_string())); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_expression_in_attribute() { + let mut parser = Parser::new(r#"
Content
"#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { attributes, .. } => { + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("classes".to_string())) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_boolean_attribute() { + let mut parser = Parser::new(""); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { attributes, .. } => { + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "disabled"); + assert_eq!(attributes[0].value, None); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_multiple_attributes() { + let mut parser = Parser::new( + r#"
{content}
"#, + ); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + attributes, + children, + .. + } => { + assert_eq!(attributes.len(), 4); + + assert_eq!(attributes[0].name, "id"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("main".to_string())) + ); + + assert_eq!(attributes[1].name, "className"); + assert_eq!( + attributes[1].value, + Some(JSXAttributeValue::Expression("classes".to_string())) + ); + + assert_eq!(attributes[2].name, "data-test"); + assert_eq!( + attributes[2].value, + Some(JSXAttributeValue::String("value".to_string())) + ); + + assert_eq!(attributes[3].name, "disabled"); + assert_eq!(attributes[3].value, None); + + assert_eq!(children.len(), 1); + assert_eq!(children[0], JSXNode::Expression("content".to_string())); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_componet() { + let mut parser = Parser::new(r#""#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, attributes, .. + } => { + assert_eq!(tag, "Custom"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "data-test"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("value".to_string())) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_component_with_underscore() { + let mut parser = Parser::new("Content"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "my_component"); + assert_eq!(children[0], JSXNode::Text("Content".to_string())); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_component_with_dollar_sign() { + let mut parser = Parser::new("<$MyComponent prop={value}>Content"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "$MyComponent"); + assert_eq!(attributes[0].name, "prop"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("value".to_string())) + ); + assert_eq!(children[0], JSXNode::Text("Content".to_string())); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_nested_underscore_components() { + let mut parser = Parser::new("Test"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "outer_comp"); + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "inner_comp"); + assert_eq!(children[0], JSXNode::Text("Test".to_string())); + } + _ => panic!("Expected inner element"), + } + } + _ => panic!("Expected outer element"), + } + } + + #[test] + fn test_parse_complex_identifier_names() { + let mut parser = Parser::new(r#"<_$MY_component_123 data-test="value" />"#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "_$MY_component_123"); + assert_eq!(attributes[0].name, "data-test"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("value".to_string())) + ); + assert_eq!(children.len(), 0); + } + e => panic!("Expected Element {:?}", e), + } + } + + #[test] + fn test_parse_nested_structure() { + let mut parser = Parser::new( + r#"
+
+

{title}

+ +
+
"#, + ); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + .. + } => { + assert_eq!(tag, "div"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("container".to_string())) + ); + + // Check header + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "header"); + + // Check h1 + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "h1"); + assert_eq!(children[0], JSXNode::Expression("title".to_string())); + } + _ => panic!("Expected h1 element"), + } + + // Check nav + match &children[1] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "nav"); + + // Check first anchor + match &children[0] { + JSXNode::Element { + tag, + attributes, + children, + .. + } => { + assert_eq!(tag, "a"); + assert_eq!(attributes[0].name, "href"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("/".to_string())) + ); + assert_eq!(children[0], JSXNode::Text("Home".to_string())); + } + _ => panic!("Expected first anchor element"), + } + + // Check second anchor + match &children[1] { + JSXNode::Element { + tag, + attributes, + children, + .. + } => { + assert_eq!(tag, "a"); + assert_eq!(attributes[0].name, "href"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("/about".to_string())) + ); + assert_eq!(children[0], JSXNode::Text("About".to_string())); + } + _ => panic!("Expected second anchor element"), + } + } + _ => panic!("Expected nav element"), + } + } + _ => panic!("Expected header element"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_list_map() { + let mut parser = Parser::new("
    {items.map(item =>
  • {item.name}
  • )}
"); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "ul"); + assert_eq!(children.len(), 1); + match &children[0] { + JSXNode::Expression(expr) => { + assert_eq!(expr, "items.map(item =>
  • {item.name}
  • )"); + } + _ => panic!("Expected Expression"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_template_string() { + let mut parser = Parser::new(r#"
    "#); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { attributes, .. } => { + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "style"); + match &attributes[0].value { + Some(JSXAttributeValue::Expression(expr)) => { + assert_eq!(expr, "`color: ${color}; background: ${bg}`"); + } + _ => panic!("Expected Expression attribute value"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_fragment_with_nested_structure() { + let input = "<>
    Content
    "; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Fragment { children } => { + assert_eq!(children.len(), 1); + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children[0], JSXNode::Text("Content".to_string())); + } + _ => panic!("Expected div element"), + } + } + _ => panic!("Expected Fragment"), + } + } + + #[test] + fn test_complex_attributes() { + let input = + r#"
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, attributes, .. + } => { + assert_eq!(tag, "div"); + assert_eq!(attributes.len(), 2); + + assert_eq!(attributes[0].name, "className"); + match &attributes[0].value { + Some(JSXAttributeValue::Expression(expr)) => { + assert_eq!(expr, "`container ${active ? 'active' : ''}`"); + } + _ => panic!("Expected className expression"), + } + + assert_eq!(attributes[1].name, "data-test"); + assert_eq!( + attributes[1].value, + Some(JSXAttributeValue::String("value".to_string())) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_spread_props() { + let input = r#""#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { attributes, .. } => { + let spread_attr = &attributes[0]; + assert_eq!(spread_attr.name, "...spreadProps"); + assert_eq!(spread_attr.value, None); + } + _ => panic!("Expected Spread Props"), + } + } + + #[test] + fn test_conditional_rendering() { + let input = r#"
    {loading ? : }
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children.len(), 1); + match &children[0] { + JSXNode::Expression(expr) => { + assert_eq!(expr, r#"loading ? : "#); + } + _ => panic!("Expected Expression"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_component_with_multiple_props() { + let input = r#""#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "CustomComponent"); + assert_eq!(attributes.len(), 3); + + assert_eq!(attributes[0].name, "prop1"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("string".to_string())) + ); + + assert_eq!(attributes[1].name, "prop2"); + assert_eq!( + attributes[1].value, + Some(JSXAttributeValue::Expression("value".to_string())) + ); + + assert_eq!(attributes[2].name, "prop3"); + assert_eq!(attributes[2].value, None); + + assert_eq!(children.len(), 0); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_list_mapping() { + let input = r#"
    {items.map((item, index) => )}
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children.len(), 1); + match &children[0] { + JSXNode::Expression(expr) => { + assert_eq!( + expr, + "items.map((item, index) => )" + ); + } + _ => panic!("Expected Expression"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_before_and_after_script() { + let input = r#"
    +

    Before Script

    + +

    After Script

    +
    "#; + + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children.len(), 3); + + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "p"); + assert_eq!(children[0], JSXNode::Text("Before Script".to_string())); + } + _ => panic!("Expected p element"), + } + + match &children[1] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "script"); + match &children[0] { + JSXNode::Text(text) => { + assert_eq!( + text.trim(), + r#"function greet() { + console.log("Hello World"); + }"# + ); + } + _ => panic!("Expected text node in script"), + } + } + _ => panic!("Expected script element"), + } + + match &children[2] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "p"); + assert_eq!(children[0], JSXNode::Text("After Script".to_string())); + } + _ => panic!("Expected p element"), + } + } + _ => panic!("Expected root element"), + } + } + + #[test] + fn test_parse_script_tag_with_html_as_string() { + let input = r#""#; + + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "script"); + assert_eq!(children.len(), 1); + + match &children[0] { + JSXNode::Text(text) => { + let expected = r#" +const copyClipboardButton = document.getElementById("js-copyClipboardButton"); +const code = copyClipboardButton.querySelector("code"); +const originalInnerHTML = copyClipboardButton.innerHTML; + +let timeout; + +copyClipboardButton.addEventListener("click", () => { +clearTimeout(timeout); +navigator.clipboard.writeText(code.textContent) +.then(() => { +copyClipboardButton.innerHTML = "Copied!"; +timeout = setTimeout(() => { +copyClipboardButton.innerHTML = originalInnerHTML; +}, 1000); +}) +.catch(err => { +console.error("Failed to copy: ", err); +}); +});"#; + assert_eq!(text.trim(), expected.trim()); + } + _ => panic!("Expected Text node"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_complex_nested_structure() { + let input = r#"
    +
    +

    {title || "Default"}

    + {loading ? ( + + ) : ( + + )} +
    +
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "div"); + assert_eq!(attributes.len(), 2); + + // Verify div attributes + assert_eq!(attributes[0].name, "className"); + match &attributes[0].value { + Some(JSXAttributeValue::Expression(expr)) => { + assert_eq!(expr, "`container ${active ? 'active' : ''}`"); + } + _ => panic!("Expected className expression"), + } + assert_eq!(attributes[1].name, "data-test"); + assert_eq!( + attributes[1].value, + Some(JSXAttributeValue::String("value".to_string())) + ); + + // Check header element + match &children[0] { + JSXNode::Element { + tag, attributes, .. + } => { + assert_eq!(tag, "header"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "id"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::String("main".to_string())) + ); + } + _ => panic!("Expected header element"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_conditional_expressions() { + let input = r#"
    + {condition &&
    Conditional Content
    } + {items.length === 0 ? : } +
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "div"); + assert_eq!(children.len(), 2); + + match &children[0] { + JSXNode::Expression(expr) => { + assert_eq!(expr, "condition &&
    Conditional Content
    "); + } + _ => panic!("Expected condition expression"), + } + + match &children[1] { + JSXNode::Expression(expr) => { + assert_eq!( + expr, + "items.length === 0 ? : " + ); + } + _ => panic!("Expected ternary expression"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_component_with_spread_props() { + let input = r#" handleEvent()} + />"#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "CustomComponent"); + assert_eq!(children.len(), 0); + + let spread_attr = &attributes[3]; + assert_eq!(spread_attr.name, "...spreadProps"); + assert_eq!(spread_attr.value, None); + + let event_attr = &attributes[4]; + assert_eq!(event_attr.name, "onEvent"); + assert_eq!( + event_attr.value, + Some(JSXAttributeValue::Expression( + "() => handleEvent()".to_string() + )) + ); + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_dynamic_attributes() { + let input = r#"
    + + {`Item ${item.name}`} + +
    "#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "section"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "data-section"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("section".to_string())) + ); + + match &children[0] { + JSXNode::Element { + tag, attributes, .. + } => { + assert_eq!(tag, "a"); + assert_eq!(attributes.len(), 2); + assert_eq!(attributes[0].name, "href"); + match &attributes[0].value { + Some(JSXAttributeValue::Expression(expr)) => { + assert_eq!(expr, "`/item/${item.id}`"); + } + _ => panic!("Expected href expression"), + } + } + _ => panic!("Expected anchor element"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_parse_complex_layout_with_conditionals() { + let input = r#"
    +
    +

    {title || "Default Title"}

    + + {user ? ( +
    + User avatar + {user.name} + +
    + ) : ( + + )} +
    +
    + {loading ? ( +
    + +
    + ) : error ? ( + + ) : ( + <>{children} + )} +
    +
    +

    © {currentYear} My Application

    +
    +
    + ;"#; + let mut parser = Parser::new(input); + let ast = parser.parse().unwrap(); + + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "div"); + assert_eq!(attributes.len(), 1); + assert_eq!(attributes[0].name, "className"); + match &attributes[0].value { + Some(JSXAttributeValue::Expression(expr)) => { + assert_eq!(expr, "`container ${theme}`"); + } + _ => panic!("Expected className expression"), + } + + // Check header + match &children[0] { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "header"); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("styles.header".to_string())) + ); + + // Check h1 + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "h1"); + assert_eq!( + children[0], + JSXNode::Expression("title || \"Default Title\"".to_string()) + ); + } + _ => panic!("Expected h1 element"), + } + + // Check nav with map expression + match &children[1] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "nav"); + match &children[0] { + JSXNode::Expression(expr) => { + assert!(expr.starts_with("menuItems.map((item, index) =>")); + } + _ => panic!("Expected map expression"), + } + } + _ => panic!("Expected nav element"), + } + + // Check conditional user menu/login button + match &children[2] { + JSXNode::Expression(expr) => { + assert!(expr.contains("user ?")); + assert!(expr.contains("className={styles.userMenu}")); + assert!(expr.contains("className={styles.loginButton}")); + } + _ => panic!("Expected conditional expression"), + } + } + _ => panic!("Expected header element"), + } + + // Check main + match &children[1] { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "main"); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("styles.main".to_string())) + ); + + // Check conditional content + match &children[0] { + JSXNode::Expression(expr) => { + assert!(expr.contains("loading ?")); + assert!(expr.contains(" panic!("Expected conditional expression"), + } + } + _ => panic!("Expected main element"), + } + + // Check footer + match &children[2] { + JSXNode::Element { + tag, + attributes, + children, + } => { + assert_eq!(tag, "footer"); + assert_eq!(attributes[0].name, "className"); + assert_eq!( + attributes[0].value, + Some(JSXAttributeValue::Expression("styles.footer".to_string())) + ); + + // Check paragraph + match &children[0] { + JSXNode::Element { tag, children, .. } => { + assert_eq!(tag, "p"); + assert_eq!(children[0], JSXNode::Text("© ".to_string())); + assert_eq!( + children[1], + JSXNode::Expression("currentYear".to_string()) + ); + assert_eq!( + children[2], + JSXNode::Text("My Application".to_string()) + ); + } + _ => panic!("Expected p element"), + } + } + _ => panic!("Expected footer element"), + } + } + _ => panic!("Expected Element"), + } + } + + #[test] + fn test_error_mismatched_tags() { + let mut parser = Parser::new("
    Hello"); + assert!(parser.parse().is_err()); + } + + #[test] + fn test_error_unclosed_tag() { + let mut parser = Parser::new("
    Hello"); + assert!(parser.parse().is_err()); + } + + #[test] + fn test_error_unclosed_expression() { + let mut parser = Parser::new("
    {count
    "); + assert!(parser.parse().is_err()); + } + + #[test] + fn test_error_invalid_identifier() { + let mut parser = Parser::new("<123>Hello"); + assert!(parser.parse().is_err()); + } + + #[test] + fn test_error_unclosed_attribute() { + let mut parser = Parser::new(r#"
    Self { + match kind { + JSXErrorKind::InvalidAttribute(msg) => { + JSXError::TransformError(format!("Invalid attribute: {}", msg)) + } + JSXErrorKind::InvalidComponent(msg) => { + JSXError::TransformError(format!("Invalid component: {}", msg)) + } + JSXErrorKind::InvalidElement(msg) => { + JSXError::TransformError(format!("Invalid element: {}", msg)) + } + JSXErrorKind::UnsupportedSyntax(msg) => { + JSXError::TransformError(format!("Unsupported syntax: {}", msg)) + } + JSXErrorKind::ExtractionError(msg) => JSXError::ExtractionError(msg), + JSXErrorKind::ParsingError(msg) => JSXError::ParsingError(msg), + } + } +} + +impl std::fmt::Display for JSXError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + JSXError::ExtractionError(msg) => write!(f, "JSX extraction error: {}", msg), + JSXError::ParsingError(msg) => write!(f, "JSX parsing error: {}", msg), + JSXError::TransformError(msg) => write!(f, "JSX transform error: {}", msg), + } + } +} + +const OPENING_BRACKET: &str = "<"; +const COMMA: &str = ","; +const UNDERSCORE: char = '_'; +const DOLLAR_SIGN: char = '$'; +const EMPTY_STRING: &str = ""; + +static SELF_CLOSING_TAGS: [&str; 23] = [ + "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", + "track", "wbr", "circle", "ellipse", "image", "line", "path", "polygon", "polyline", "rect", + "use", +]; + +#[inline] +fn is_component(tag: &str) -> bool { + tag.chars() + .next() + .map(|c| c.is_uppercase() || c == UNDERSCORE || c == DOLLAR_SIGN) + .unwrap_or(false) +} + +#[inline] +fn is_self_closing(tag: &str) -> bool { + SELF_CLOSING_TAGS.contains(&tag.to_lowercase().as_str()) +} + +pub fn jsx_precompile(source: &str) -> Result { + let (mut result, store, key_counter) = extract_string_html(source)?; + + let locations = extract_jsx_locations(&result)?; + let templates = transform_jsx_elements(locations)?; + result = replace_jsx_placeholders(result, templates); + result = restore_string_placeholders(result, store, key_counter); + + // Remove empty interpolation artifacts + Ok(result.replace("${}", EMPTY_STRING)) +} + +fn extract_string_html(source: &str) -> Result<(String, HashMap, usize), JSXError> { + let mut store: HashMap = HashMap::new(); + let mut key_counter = 0; + let mut result = source.to_string(); + + let regex = regex::Regex::new(r#"StringHTML\((.*?)\)"#) + .map_err(|e| JSXError::ExtractionError(e.to_string()))?; + + for caps in regex.captures_iter(source) { + let str_key = format!("__str_{}", key_counter); + let value = caps + .get(1) + .ok_or_else(|| JSXError::ExtractionError("Failed to capture value".to_string()))? + .as_str() + .to_string(); + + store.insert(str_key.clone(), value.clone()); + result = result.replace(&format!("StringHTML({})", value), &str_key); + key_counter += 1; + } + + Ok((result, store, key_counter)) +} + +fn extract_jsx_locations(source: &str) -> Result, JSXError> { + let mut extractor = JSXExtractor::new(source.to_owned()); + extractor + .extract() + .map_err(|e| JSXError::with_kind(JSXErrorKind::ExtractionError(e.to_string()))) +} + +fn transform_jsx_elements(locations: Vec) -> Result, JSXError> { + let mut templates: HashMap = HashMap::new(); + + for location in locations { + let content = location.trim().to_string(); + let mut parser = Parser::new(&content); + + match parser.parse() { + Ok(ast) => { + let template = transform_to_template(&ast)?; + templates.insert(content, template); + } + Err(e) => { + return Err(JSXError::with_kind(JSXErrorKind::ParsingError(format!( + "{}. {}", + e, content + )))); + } + } + } + + Ok(templates) +} + +#[inline] +fn replace_jsx_placeholders(mut result: String, templates: HashMap) -> String { + for (key, template) in templates { + let template = template.split_whitespace().collect::>().join(" "); + let transformed = if template.contains(".map(") + || template.contains(".filter(") + || template.contains(".reduce(") + { + format!("`${{__jsxTemplate(`{}`)}}`", template) + } else { + format!("`{}`", template) + }; + + result = result + .replace(&format!("`{key}`"), &transformed) + .replace(&key, &transformed); + } + result +} + +#[inline] +fn restore_string_placeholders( + mut result: String, + store: HashMap, + key_counter: usize, +) -> String { + for i in 0..key_counter { + let str_key = format!("__str_{}", i); + if let Some(value) = store.get(&str_key) { + result = result.replace(&str_key, value); + } + } + result +} + +fn transform_to_template(ast: &JSXNode) -> Result { + match ast { + JSXNode::Element { + tag, + attributes, + children, + } => { + if is_component(tag) { + transform_component(tag, attributes, children) + } else { + transform_element(tag, attributes, children) + } + } + JSXNode::Expression(expr) => Ok(format!(r#"${{{}}}"#, expr)), + JSXNode::Fragment { children } => transform_fragment(children), + JSXNode::Text(text) => Ok(text.to_string()), + } +} + +fn transform_component( + tag: &str, + attributes: &[JSXAttribute], + children: &[JSXNode], +) -> Result { + let attr_parts = transform_component_attributes(attributes)?; + let children_parts = transform_component_children(children)?; + + if children_parts.is_empty() { + Ok(format!(r#"${{__jsxComponent({}, {})}}"#, tag, attr_parts)) + } else { + let children_str = children_parts.join(""); + Ok(format!( + r#"${{__jsxComponent({}, {}, `{}`)}}"#, + tag, + attr_parts, + children_str.trim() + )) + } +} + +fn transform_component_attributes(attributes: &[JSXAttribute]) -> Result { + let mut attr_parts = Vec::new(); + for attr in attributes.iter() { + match &attr.value { + Some(JSXAttributeValue::Expression(expr)) => { + attr_parts.push(format!(r#"{{"{}":{}}}"#, &attr.name, expr)); + } + Some(JSXAttributeValue::String(value)) => { + attr_parts.push(format!(r#"{{"{}":"{}"}}"#, &attr.name, value)); + } + None => { + if attr.name.starts_with("...") { + attr_parts.push(format!( + r#"`${{__jsxSpread({})}}`"#, + attr.name.replace("...", "") + )); + } else { + attr_parts.push(format!(r#"`{}`"#, attr.name)); + } + } + } + } + Ok(format!("[{}]", attr_parts.join(COMMA))) +} + +#[inline] +fn transform_component_children(children: &[JSXNode]) -> Result, JSXError> { + let mut children_parts = Vec::new(); + for child in children { + children_parts.push(transform_to_template(child)?); + } + Ok(children_parts) +} + +fn transform_element( + tag: &str, + attributes: &[JSXAttribute], + children: &[JSXNode], +) -> Result { + let attrs = transform_element_attributes(attributes)?; + let attrs_str = if !attrs.is_empty() { + format!(" {}", attrs.join(" ")) + } else { + String::new() + }; + + if is_self_closing(tag) { + return Ok(format!("<{}{} />", tag, attrs_str)); + } + + let mut children_parts = Vec::new(); + for child in children { + match child { + JSXNode::Text(text) => { + children_parts.push(text.to_string()); + } + JSXNode::Expression(expr) => { + if expr.contains(OPENING_BRACKET) { + let nested = jsx_precompile(expr)?; + children_parts.push(format!("${{{}}}", nested)); + } else { + children_parts.push(format!("${{{}}}", expr)); + } + } + _ => { + let child_content = transform_to_template(child)?; + children_parts.push(child_content); + } + } + } + + let children_str = children_parts.join(""); + Ok(format!("<{}{}>{}", tag, attrs_str, children_str, tag)) +} + +fn transform_element_attributes(attributes: &[JSXAttribute]) -> Result, JSXError> { + let mut attr_parts = Vec::new(); + for attr in attributes { + let name = normalize_html_attr_name(&attr.name); + + match &attr.value { + Some(JSXAttributeValue::Expression(expr)) => { + attr_parts.push(format!(r#"{}="${{{}}}""#, name, expr)); + } + Some(JSXAttributeValue::String(value)) => { + attr_parts.push(format!(r#"{}="{}""#, name, value)); + } + None => { + if attr.name.starts_with("...") { + attr_parts.push(format!( + "${{__jsxSpread({})}}", + attr.name.replace("...", "") + )); + } else { + attr_parts.push(attr.name.to_string()); + } + } + } + } + Ok(attr_parts) +} + +#[inline] +fn transform_fragment(children: &[JSXNode]) -> Result { + let mut children_parts = Vec::new(); + for child in children { + let content = transform_to_template(child)?; + children_parts.push(content); + } + Ok(children_parts.join("")) +} + +// @see: https://github.com/denoland/deno_ast/blob/3aba071b59d71802398c2fbcd2d01c99a51553cf/src/transpiling/jsx_precompile.rs#L89 +#[inline] +fn normalize_html_attr_name(name: &str) -> String { + match name { + // JSX specific + "htmlFor" => "for".to_string(), + "className" => "class".to_string(), + "dangerouslySetInnerHTML" => name.to_string(), + + "panose1" => "panose-1".to_string(), + "xlinkActuate" => "xlink:actuate".to_string(), + "xlinkArcrole" => "xlink:arcrole".to_string(), + + // xlink:href was removed from SVG and isn't needed + "xlinkHref" => "href".to_string(), + "xlink:href" => "href".to_string(), + + "xlinkRole" => "xlink:role".to_string(), + "xlinkShow" => "xlink:show".to_string(), + "xlinkTitle" => "xlink:title".to_string(), + "xlinkType" => "xlink:type".to_string(), + "xmlBase" => "xml:base".to_string(), + "xmlLang" => "xml:lang".to_string(), + "xmlSpace" => "xml:space".to_string(), + + // Attributes that are kebab-cased + "accentHeight" + | "acceptCharset" + | "alignmentBaseline" + | "arabicForm" + | "baselineShift" + | "capHeight" + | "clipPath" + | "clipRule" + | "colorInterpolation" + | "colorInterpolationFilters" + | "colorProfile" + | "colorRendering" + | "contentScriptType" + | "contentStyleType" + | "dominantBaseline" + | "enableBackground" + | "fillOpacity" + | "fillRule" + | "floodColor" + | "floodOpacity" + | "fontFamily" + | "fontSize" + | "fontSizeAdjust" + | "fontStretch" + | "fontStyle" + | "fontVariant" + | "fontWeight" + | "glyphName" + | "glyphOrientationHorizontal" + | "glyphOrientationVertical" + | "horizAdvX" + | "horizOriginX" + | "horizOriginY" + | "httpEquiv" + | "imageRendering" + | "letterSpacing" + | "lightingColor" + | "markerEnd" + | "markerMid" + | "markerStart" + | "overlinePosition" + | "overlineThickness" + | "paintOrder" + | "pointerEvents" + | "renderingIntent" + | "shapeRendering" + | "stopColor" + | "stopOpacity" + | "strikethroughPosition" + | "strikethroughThickness" + | "strokeDasharray" + | "strokeDashoffset" + | "strokeLinecap" + | "strokeLinejoin" + | "strokeMiterlimit" + | "strokeOpacity" + | "strokeWidth" + | "textAnchor" + | "textDecoration" + | "textRendering" + | "transformOrigin" + | "underlinePosition" + | "underlineThickness" + | "unicodeBidi" + | "unicodeRange" + | "unitsPerEm" + | "vAlphabetic" + | "vectorEffect" + | "vertAdvY" + | "vertOriginX" + | "vertOriginY" + | "vHanging" + | "vMathematical" + | "wordSpacing" + | "writingMode" + | "xHeight" => name + .chars() + .map(|ch| match ch { + 'A'..='Z' => format!("-{}", ch.to_lowercase()), + _ => ch.to_string(), + }) + .collect(), + + // Attributes that are camelCased and should be kept as is. + "allowReorder" + | "attributeName" + | "attributeType" + | "baseFrequency" + | "baseProfile" + | "calcMode" + | "clipPathUnits" + | "diffuseConstant" + | "edgeMode" + | "filterUnits" + | "glyphRef" + | "gradientTransform" + | "gradientUnits" + | "kernelMatrix" + | "kernelUnitLength" + | "keyPoints" + | "keySplines" + | "keyTimes" + | "lengthAdjust" + | "limitingConeAngle" + | "markerHeight" + | "markerUnits" + | "markerWidth" + | "maskContentUnits" + | "maskUnits" + | "numOctaves" + | "pathLength" + | "patternContentUnits" + | "patternTransform" + | "patternUnits" + | "pointsAtX" + | "pointsAtY" + | "pointsAtZ" + | "preserveAlpha" + | "preserveAspectRatio" + | "primitiveUnits" + | "referrerPolicy" + | "refX" + | "refY" + | "repeatCount" + | "repeatDur" + | "requiredExtensions" + | "requiredFeatures" + | "specularConstant" + | "specularExponent" + | "spreadMethod" + | "startOffset" + | "stdDeviation" + | "stitchTiles" + | "surfaceScale" + | "systemLanguage" + | "tableValues" + | "targetX" + | "targetY" + | "textLength" + | "viewBox" + | "xChannelSelector" + | "yChannelSelector" + | "zoomAndPan" => name.to_string(), + + _ => { + // Devs expect attributes in the HTML document to be lowercased. + name.to_lowercase() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_jsx_element() { + let source = "const el =
    Hello
    ;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!(result, "const el = `
    Hello
    `;"); + } + + #[test] + fn test_jsx_with_attributes() { + let source = "const el =
    Content
    ;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `
    Content
    `;" + ); + } + + #[test] + fn test_boolean_attribute_element() { + let source = r#"const el = ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + r#"const el = ``;"# + ); + } + + #[test] + fn test_normalize_html_attr_name() { + let values = HashMap::from([ + ("accentHeight", "accent-height"), + ("acceptCharset", "accept-charset"), + ("alignmentBaseline", "alignment-baseline"), + ("allowReorder", "allowReorder"), + ("arabicForm", "arabic-form"), + ("attributeName", "attributeName"), + ("attributeType", "attributeType"), + ("baseFrequency", "baseFrequency"), + ("baselineShift", "baseline-shift"), + ("baseProfile", "baseProfile"), + ("calcMode", "calcMode"), + ("capHeight", "cap-height"), + ("className", "class"), + ("clipPath", "clip-path"), + ("clipPathUnits", "clipPathUnits"), + ("clipRule", "clip-rule"), + ("colorInterpolation", "color-interpolation"), + ("colorInterpolationFilters", "color-interpolation-filters"), + ("colorProfile", "color-profile"), + ("colorRendering", "color-rendering"), + ("contentScriptType", "content-script-type"), + ("contentStyleType", "content-style-type"), + ("diffuseConstant", "diffuseConstant"), + ("dominantBaseline", "dominant-baseline"), + ("edgeMode", "edgeMode"), + ("enableBackground", "enable-background"), + ("fillOpacity", "fill-opacity"), + ("fillRule", "fill-rule"), + ("filterUnits", "filterUnits"), + ("floodColor", "flood-color"), + ("floodOpacity", "flood-opacity"), + ("fontFamily", "font-family"), + ("fontSize", "font-size"), + ("fontSizeAdjust", "font-size-adjust"), + ("fontStretch", "font-stretch"), + ("fontStyle", "font-style"), + ("fontVariant", "font-variant"), + ("fontWeight", "font-weight"), + ("glyphName", "glyph-name"), + ("glyphOrientationHorizontal", "glyph-orientation-horizontal"), + ("glyphOrientationVertical", "glyph-orientation-vertical"), + ("glyphRef", "glyphRef"), + ("gradientTransform", "gradientTransform"), + ("gradientUnits", "gradientUnits"), + ("horizAdvX", "horiz-adv-x"), + ("horizOriginX", "horiz-origin-x"), + ("horizOriginY", "horiz-origin-y"), + ("htmlFor", "for"), + ("httpEquiv", "http-equiv"), + ("imageRendering", "image-rendering"), + ("kernelMatrix", "kernelMatrix"), + ("kernelUnitLength", "kernelUnitLength"), + ("keyPoints", "keyPoints"), + ("keySplines", "keySplines"), + ("keyTimes", "keyTimes"), + ("lengthAdjust", "lengthAdjust"), + ("letterSpacing", "letter-spacing"), + ("lightingColor", "lighting-color"), + ("limitingConeAngle", "limitingConeAngle"), + ("markerEnd", "marker-end"), + ("markerHeight", "markerHeight"), + ("markerMid", "marker-mid"), + ("markerStart", "marker-start"), + ("markerUnits", "markerUnits"), + ("markerWidth", "markerWidth"), + ("maskContentUnits", "maskContentUnits"), + ("maskUnits", "maskUnits"), + ("numOctaves", "numOctaves"), + ("overlinePosition", "overline-position"), + ("overlineThickness", "overline-thickness"), + ("paintOrder", "paint-order"), + ("panose1", "panose-1"), + ("pathLength", "pathLength"), + ("patternContentUnits", "patternContentUnits"), + ("patternTransform", "patternTransform"), + ("patternUnits", "patternUnits"), + ("pointsAtX", "pointsAtX"), + ("pointsAtY", "pointsAtY"), + ("pointsAtZ", "pointsAtZ"), + ("pointerEvents", "pointer-events"), + ("preserveAlpha", "preserveAlpha"), + ("preserveAspectRatio", "preserveAspectRatio"), + ("primitiveUnits", "primitiveUnits"), + ("referrerPolicy", "referrerPolicy"), + ("refX", "refX"), + ("refY", "refY"), + ("renderingIntent", "rendering-intent"), + ("repeatCount", "repeatCount"), + ("repeatDur", "repeatDur"), + ("requiredExtensions", "requiredExtensions"), + ("requiredFeatures", "requiredFeatures"), + ("shapeRendering", "shape-rendering"), + ("specularConstant", "specularConstant"), + ("specularExponent", "specularExponent"), + ("spreadMethod", "spreadMethod"), + ("startOffset", "startOffset"), + ("stdDeviation", "stdDeviation"), + ("stitchTiles", "stitchTiles"), + ("stopColor", "stop-color"), + ("stopOpacity", "stop-opacity"), + ("strikethroughPosition", "strikethrough-position"), + ("strikethroughThickness", "strikethrough-thickness"), + ("strokeDasharray", "stroke-dasharray"), + ("strokeDashoffset", "stroke-dashoffset"), + ("strokeLinecap", "stroke-linecap"), + ("strokeLinejoin", "stroke-linejoin"), + ("strokeMiterlimit", "stroke-miterlimit"), + ("strokeOpacity", "stroke-opacity"), + ("strokeWidth", "stroke-width"), + ("surfaceScale", "surfaceScale"), + ("systemLanguage", "systemLanguage"), + ("tableValues", "tableValues"), + ("targetX", "targetX"), + ("targetY", "targetY"), + ("textAnchor", "text-anchor"), + ("textDecoration", "text-decoration"), + ("textLength", "textLength"), + ("textRendering", "text-rendering"), + ("transformOrigin", "transform-origin"), + ("underlinePosition", "underline-position"), + ("underlineThickness", "underline-thickness"), + ("unicodeBidi", "unicode-bidi"), + ("unicodeRange", "unicode-range"), + ("unitsPerEm", "units-per-em"), + ("vAlphabetic", "v-alphabetic"), + ("viewBox", "viewBox"), + ("vectorEffect", "vector-effect"), + ("vertAdvY", "vert-adv-y"), + ("vertOriginX", "vert-origin-x"), + ("vertOriginY", "vert-origin-y"), + ("vHanging", "v-hanging"), + ("vMathematical", "v-mathematical"), + ("wordSpacing", "word-spacing"), + ("writingMode", "writing-mode"), + ("xChannelSelector", "xChannelSelector"), + ("xHeight", "x-height"), + ("xlinkActuate", "xlink:actuate"), + ("xlinkArcrole", "xlink:arcrole"), + ("xlinkHref", "href"), + ("xlink:href", "href"), + ("xlinkRole", "xlink:role"), + ("xlinkShow", "xlink:show"), + ("xlinkTitle", "xlink:title"), + ("xlinkType", "xlink:type"), + ("xmlBase", "xml:base"), + ("xmlLang", "xml:lang"), + ("xmlSpace", "xml:space"), + ("yChannelSelector", "yChannelSelector"), + ("zoomAndPan", "zoomAndPan"), + ]); + + for (input, expected) in values { + assert_eq!(normalize_html_attr_name(input), expected); + } + } + + #[test] + fn test_jsx_with_dynamic_attributes() { + let source = "const el =
    Content
    ;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `
    Content
    `;" + ); + } + + #[test] + fn test_label_html_for() { + let source = r#"const el = ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = ``;" + ); + } + + #[test] + fn test_self_close_element() { + let source = + r#"const el = ;"#; + let result = jsx_precompile(source).unwrap(); + let expected = + "const el = ``;"; + assert_eq!(result, expected); + } + + #[test] + fn test_self_close_component() { + let source = r#"const el =
    ;"#; + let result = jsx_precompile(source).unwrap(); + let expected = "const el = `
    ${__jsxComponent(Spinner, [{\"size\":\"large\"},{\"color\":theme === 'dark' ? 'white' : 'black'}])}
    `;"; + assert_eq!(result, expected); + } + + #[test] + fn test_underscore_components() { + let source = "const el = <_CustomComponent prop=\"value\">child;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxComponent(_CustomComponent, [{\"prop\":\"value\"}], `child`)}`;" + ); + } + + #[test] + fn test_dollar_sign_components() { + let source = "const el = <$Component {...props}>content;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxComponent($Component, [`${__jsxSpread(props)}`], `content`)}`;" + ); + } + + #[test] + fn test_nested_special_components() { + let source = "const el = <_Parent><$Child>nested;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxComponent(_Parent, [], `${__jsxComponent($Child, [], `nested`)}`)}`;" + ); + } + + #[test] + fn test_component_with_multiple_attributes() { + let source = r#"const el = Content;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxComponent(Component, [{\"className\":\"test-class\"},{\"htmlFor\":\"input-id\"},{\"onClick\":handleClick}], `Content`)}`;" + ); + } + + #[test] + fn test_html_doctype() { + let source = + r#"const el = `${StringHTML(`${html}`)}`;"#; + let result = jsx_precompile(source).unwrap(); + let expected = r#"const el = `${`${html}`}`;"#; + assert_eq!(result, expected); + } + + #[test] + fn test_nested_components_and_element() { + let source = r#"const el =
    Inner content
    ;"#; + let result = jsx_precompile(source).unwrap(); + let expected = "const el = `${__jsxComponent(ParentComponent, [{\"attr\":\"val\"},`moto`], `${__jsxComponent(ChildComponent, [`${__jsxSpread(spread)}`], `
    Inner content
    `)}`)}`;"; + assert_eq!(result, expected); + } + + #[test] + fn test_fragment_with_nested_components() { + let source = r#"const el = <>Head
    Inner
    ;"#; + let result = jsx_precompile(source).unwrap(); + let expected = r#"const el = `${__jsxComponent(A, [], `Head`)}${__jsxComponent(A, [{"class":"n"}], `${__jsxComponent(B, [], `
    Inner
    `)}`)}`;"#; + assert_eq!(result, expected); + } + + #[test] + fn test_seo_component() { + let source = r#"const el = Query - An All-In-One Solution For Your Side-Projects And Startups;"#; + let result = jsx_precompile(source).unwrap(); + let expected = "const el = `${__jsxComponent(A, [], `Query - An All-In-One Solution For Your Side-Projects And Startups`)}`;"; + + assert_eq!(result, expected); + } + + #[test] + fn test_nested_elements() { + let source = r#"const el =
    Nested 1Nested 2
    ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `
    Nested 1Nested 2
    `;" + ); + } + + #[test] + fn test_dynamic_content() { + let source = "const el =
    {dynamicContent}
    ;"; + let result = jsx_precompile(source).unwrap(); + assert_eq!(result, "const el = `
    ${dynamicContent}
    `;"); + } + + #[test] + fn test_array_transformations() { + let source = r#"const el =
    {items.map(item =>
  • {item}
  • )}
    ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxTemplate(`
    ${items.map(item => `
  • ${item}
  • `)}
    `)}`;" + ); + } + + #[test] + fn test_filter_transformation() { + let source = r#"const el =
    {items.filter(item => item.active).map(item =>
  • {item.name}
  • )}
    ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxTemplate(`
    ${items.filter(item => item.active).map(item => `
  • ${item.name}
  • `)}
    `)}`;" + ); + } + + #[test] + fn test_reduce_transformation() { + let source = r#"const el =
    {items.reduce((acc, item) => acc + item, 0)}
    ;"#; + let result = jsx_precompile(source).unwrap(); + assert_eq!( + result, + "const el = `${__jsxTemplate(`
    ${items.reduce((acc, item) => acc + item, 0)}
    `)}`;" + ); + } + + #[test] + fn test_complex_jsx() { + let source = r#"const TodoList = ({items, onToggle}) => ( +
    +
    +

    {items.length} Tasks Remaining

    + +
    +
      +{items.map((item, index) => ( +
    • + onToggle(index)} +/> +{item.text} + +
    • +))} +
    +
    )"# + .trim(); + + let result = jsx_precompile(source).unwrap(); + let expected = "const TodoList = ({items, onToggle}) => (\n`${__jsxTemplate(`

    ${items.length}Tasks Remaining

      ${items.map((item, index) => ( `
    • onToggle(index)}\" />${item.text}
    • ` ))}
    `)}`)"; + + assert_eq!(result, expected); + } + + #[test] + fn test_complex_jsx_with_conditions() { + let input = r#"const el =
    +
    +

    {title || "Default Title"}

    + + {user ? ( +
    + User avatar + {user.name} + +
    + ) : ( + + )} +
    +
    + {loading ? ( +
    + +
    + ) : error ? ( + + ) : ( + <>{children} + )} +
    +
    +

    © {currentYear} My Application

    +
    +
    + ;"#; + let result = jsx_precompile(input).unwrap(); + let expected = "const el = `${__jsxTemplate(`

    ${title || \"Default Title\"}

    ${user ? ( `
    \"User${user.name}
    ` ) : ( `` )}
    ${loading ? ( `
    ${__jsxComponent(Spinner, [{\"size\":\"large\"},{\"color\":theme === 'dark' ? 'white' : 'black'}])}
    ` ) : error ? ( `${__jsxComponent(ErrorMessage, [{\"message\":error},{\"onRetry\":handleRetry}])}` ) : ( `${children}` )}

    © ${currentYear}My Application

    `)}`\n ;"; + assert_eq!(result, expected); + } +} diff --git a/crates/jsx_parser/src/lib.rs b/crates/jsx_parser/src/lib.rs new file mode 100644 index 00000000..2ddf4832 --- /dev/null +++ b/crates/jsx_parser/src/lib.rs @@ -0,0 +1,3 @@ +pub mod jsx_parser; +mod jsx_extractor; +pub mod jsx_precompile; diff --git a/crates/runtime/src/js/jsx-helpers.js b/crates/runtime/src/js/jsx-helpers.js new file mode 100644 index 00000000..3f1ad1cb --- /dev/null +++ b/crates/runtime/src/js/jsx-helpers.js @@ -0,0 +1,28 @@ +function __jsxTemplate(string) { + return string.replace(/>,\s*<"); +} + +function __jsxComponent(Component, props, children) { + const finalProps = Array.isArray(props) ? props.reduce((acc, prop) => Object.assign(acc, prop), {}) : props; + return Component({ children, ...finalProps }); +} + +function __jsxSpread(obj) { + const result = []; + for (const [propKey, propValue] of Object.entries(obj)) { + if (propValue === null || propValue === undefined) continue; + + if (typeof propValue === "boolean") { + if (propValue) { + result.push(propKey); + } + continue; + } + result.push(`${propKey}="${propValue}"`); + } + return result.join(" "); +} + +globalThis.__jsxComponent = __jsxComponent; +globalThis.__jsxSpread = __jsxSpread; +globalThis.__jsxTemplate = __jsxTemplate; diff --git a/crates/runtime/src/js/jsx-helpers.test.js b/crates/runtime/src/js/jsx-helpers.test.js new file mode 100644 index 00000000..e43cd069 --- /dev/null +++ b/crates/runtime/src/js/jsx-helpers.test.js @@ -0,0 +1,117 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; + +import "./jsx-helpers.js"; + +describe("__jsxTemplate", () => { + it("should handle basic static template", () => { + const result = __jsxTemplate("
    Hello
    "); + assert.equal(result.toString(), "
    Hello
    "); + }); + + it("should handle template with dynamic values", () => { + const dynamicClass = "active"; + const result = __jsxTemplate(`
    Content
    `); + assert.equal(result.toString(), '
    Content
    '); + }); + + it("should handle nested elements with dynamic content", () => { + const items = ["one", "two"]; + const result = __jsxTemplate(`
      ${items.map((item) => __jsxTemplate(`
    • ${item}
    • `))}
    `); + assert.equal(result.toString(), '
    • one
    • two
    '); + }); +}); + +describe("__jsxComponent", () => { + it("should handle component with no props", () => { + const Component = () => "
    Basic Component
    "; + const result = __jsxComponent(Component, []); + assert.equal(result.toString(), "
    Basic Component
    "); + }); + + it("should handle component with props", () => { + const Component = (props) => `
    With Props
    `; + const result = __jsxComponent(Component, [{ class: "test-class" }]); + assert.equal(result.toString(), '
    With Props
    '); + }); + + it("should handle component with children", () => { + const Component = ({ children }) => `
    ${children}
    `; + const result = __jsxComponent(Component, [], "Child Content"); + assert.equal(result.toString(), "
    Child Content
    "); + }); + + it("should handle component with props and children", () => { + const Component = ({ children, ...props }) => `
    ${children}
    `; + const result = __jsxComponent(Component, [{ id: "test" }], "Child Content"); + assert.equal(result.toString(), '
    Child Content
    '); + }); +}); + +describe("__jsxSpread", () => { + it("should spread object properties into string", () => { + const props = { class: "test", id: "main", disabled: true }; + const result = __jsxSpread(props); + assert.equal(result.toString(), 'class="test" id="main" disabled'); + }); + + it("should handle empty object", () => { + const props = {}; + const result = __jsxSpread(props); + assert.equal(result.toString(), ""); + }); + + it("should handle boolean and null values", () => { + const props = { + visible: true, + hidden: false, + empty: null, + zero: 0, + }; + const result = __jsxSpread(props); + assert.equal(result.toString(), 'visible zero="0"'); + }); + + it("should handle complex jsx with conditions", () => { + const mockMenuItems = [ + { href: "/home", icon: "home", label: "Home" }, + { href: "/profile", icon: "user", label: "Profile", badge: 3, badgeType: "notification" }, + ]; + const mockUser = { avatar: "/avatar.jpg", name: "John Doe" }; + const mockTheme = "light"; + const mockCurrentPath = "/home"; + const mockStyles = { header: "header-class", link: "link-class", active: "active-class" }; + + const Icon = (props) => `${props.name}`; + const Badge = (props) => `${props.count}`; + + const result = __jsxTemplate(` +
    +
    +

    ${"Default Title"}

    + + ${ + mockUser + ? __jsxTemplate(`
    + User avatar + ${mockUser.name} + +
    `) + : __jsxTemplate(``) + } +
    +
    + `); + + const expected = + '

    Default Title

    User avatarJohn Doe
    '; + + assert.equal(result.toString().replace(/\s+/g, ""), expected.replace(/\s+/g, "")); + }); +}); diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 0f9952dd..8f5145d7 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -69,6 +69,7 @@ impl std::fmt::Debug for Runtime { const DATABASE_SCRIPT_MODULE: &str = include_str!("js/database.js"); const EMAIL_SCRIPT_MODULE: &str = include_str!("js/email.js"); const HANDLE_RESPONSE_SCRIPT_MODULE: &str = include_str!("js/handle-response.js"); +const JSX_HELPERS_SCRIPT_MODULE: &str = include_str!("js/jsx-helpers.js"); const PLUGIN_SCRIPT_MODULE: &str = include_str!("js/plugin.js"); // Polyfill modules const BLOB_SCRIPT_MODULE: &str = include_str!("js/polyfills/blob.js"); @@ -99,6 +100,7 @@ impl Runtime { let resolver = BuiltinResolver::default() .with_module("js/database") .with_module("js/handle-response") + .with_module("js/jsx-helpers") .with_module("polyfill/blob") .with_module("polyfill/console") .with_module("polyfill/fetch") @@ -114,6 +116,7 @@ impl Runtime { BuiltinLoader::default() .with_module("js/database", DATABASE_SCRIPT_MODULE) .with_module("js/handle-response", HANDLE_RESPONSE_SCRIPT_MODULE) + .with_module("js/jsx-helpers", JSX_HELPERS_SCRIPT_MODULE) .with_module("polyfill/blob", BLOB_SCRIPT_MODULE) .with_module("polyfill/console", CONSOLE_SCRIPT_MODULE) .with_module("polyfill/fetch", FETCH_SCRIPT_MODULE) diff --git a/crates/server/src/controllers/functions/function.rs b/crates/server/src/controllers/functions/function.rs index 82714b37..677a3d79 100644 --- a/crates/server/src/controllers/functions/function.rs +++ b/crates/server/src/controllers/functions/function.rs @@ -190,6 +190,7 @@ pub async fn function(req: &mut Request) -> Result, import 'js/database'; import 'js/handle-response'; + import 'js/jsx-helpers'; {function} "#, diff --git a/index.d.ts b/index.d.ts index dc6b96b9..f2996554 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,2 +1,20 @@ export type * from "./crates/runtime/src/js/database"; export type * from "./crates/runtime/src/js/email"; + +declare global { + // NOTE: To avoid editor ts error + namespace JSX { + interface Element { + type: string; + props: { [key: string]: unknown }; + children: unknown[]; + } + + interface IntrinsicElements { + [elemName: string]: unknown; + } + } + type ComponentChild = object | string | number | bigint | boolean | null | undefined; + type ComponentChildren = ComponentChild[] | ComponentChild; + const StringHTML: (input: string) => string; +}